mirror of
https://github.com/facebook/rocksdb.git
synced 2026-11-06 19:12:36 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3c27a3dde0 | ||
|
|
0d378ba066 | ||
|
|
520436ad5c | ||
|
|
0e2801ac30 | ||
|
|
2647d5c661 | ||
|
|
11f21cf86b | ||
|
|
eca4f106bb | ||
|
|
5bb363edc7 | ||
|
|
b5cde68b8a | ||
|
|
d9787264a8 | ||
|
|
2fef013616 | ||
|
|
a245672710 | ||
|
|
786ac6a0e9 | ||
|
|
ade981b5b4 | ||
|
|
84432c6580 | ||
|
|
56bc1a0e83 | ||
|
|
9af0d6eafa | ||
|
|
a617c14d2a | ||
|
|
a1a102ffce | ||
|
|
5f4a8c3da4 | ||
|
|
6549b11714 | ||
|
|
71e38dbe25 | ||
|
|
54ace7f340 | ||
|
|
98c33cb8e3 | ||
|
|
1238120fe6 | ||
|
|
10984e8c26 | ||
|
|
0611eb5b9d | ||
|
|
f411c8bc97 | ||
|
|
f97e33454f | ||
|
|
a164576252 | ||
|
|
8648fbcba3 | ||
|
|
0e04ef1a96 | ||
|
|
40adb2bab7 | ||
|
|
cabd2d8718 | ||
|
|
e490f2b051 | ||
|
|
43bc71fef6 | ||
|
|
55ac0b729e | ||
|
|
f48b64460e | ||
|
|
0c6e9c036a | ||
|
|
a24574e80a | ||
|
|
0bea5a2cfe | ||
|
|
4577b672d5 | ||
|
|
0d3aaf7c0f | ||
|
|
4b1d595306 | ||
|
|
064c0ad53d | ||
|
|
c989c51ed7 | ||
|
|
cd6f802ccb | ||
|
|
92ad4a88f3 | ||
|
|
f31b4d80ff | ||
|
|
0082907bf2 | ||
|
|
d96e67c2bf | ||
|
|
4eb5878ab2 | ||
|
|
8ac14f525e | ||
|
|
96340dbce2 | ||
|
|
d6aed64de4 | ||
|
|
f5c5f881d2 | ||
|
|
c62de54c7c | ||
|
|
4df71db246 | ||
|
|
945f60b157 | ||
|
|
ecb844babd | ||
|
|
81d52bdc1a | ||
|
|
d223d34bf3 | ||
|
|
5b8f5cbcf4 |
No files matched your search
@@ -1,13 +1,13 @@
|
||||
name: facebook/rocksdb/benchmark-linux
|
||||
on: workflow_dispatch
|
||||
jobs:
|
||||
# FIXME: when this job is fixed, it should be given a cron schedule like
|
||||
permissions: {}
|
||||
# FIXME: Disabled temporarily
|
||||
# schedule:
|
||||
# - cron: 0 * * * *
|
||||
# workflow_dispatch:
|
||||
# - cron: 7 */2 * * * # At minute 7 past every 2nd hour
|
||||
jobs:
|
||||
benchmark-linux:
|
||||
if: ${{ github.repository_owner == 'facebook' }}
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-latest # FIXME: change this back to self-hosted when ready
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- uses: "./.github/actions/build-for-benchmarks"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
name: facebook/rocksdb/nightly
|
||||
on: workflow_dispatch
|
||||
permissions: {}
|
||||
jobs:
|
||||
# These jobs would be in nightly but are failing or otherwise broken for
|
||||
# some reason.
|
||||
|
||||
@@ -3,6 +3,7 @@ on:
|
||||
schedule:
|
||||
- cron: 0 9 * * *
|
||||
workflow_dispatch:
|
||||
permissions: {}
|
||||
jobs:
|
||||
build-format-compatible:
|
||||
if: ${{ github.repository_owner == 'facebook' }}
|
||||
@@ -79,7 +80,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- uses: "./.github/actions/pre-steps"
|
||||
- run: PORTABLE=1 make V=1 -j32 valgrind_test
|
||||
- run: make V=1 -j32 valgrind_test
|
||||
- uses: "./.github/actions/post-steps"
|
||||
build-windows-vs2022-avx2:
|
||||
if: ${{ github.repository_owner == 'facebook' }}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
name: facebook/rocksdb/pr-jobs-candidate
|
||||
on: workflow_dispatch
|
||||
permissions: {}
|
||||
jobs:
|
||||
# These jobs would be in pr-jobs but are failing or otherwise broken for
|
||||
# some reason.
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
name: facebook/rocksdb/pr-jobs
|
||||
on: [push, pull_request]
|
||||
permissions: {}
|
||||
jobs:
|
||||
# NOTE: multiple workflows would be recommended, but the current GHA UI in
|
||||
# PRs doesn't make it clear when there's an overall error with a workflow,
|
||||
|
||||
+60
@@ -1,6 +1,66 @@
|
||||
# Rocksdb Change Log
|
||||
> NOTE: Entries for next release do not go here. Follow instructions in `unreleased_history/README.txt`
|
||||
|
||||
## 9.7.4 (10/31/2024)
|
||||
### Bug Fixes
|
||||
* Fix a leak of obsolete blob files left open until DB::Close(). This bug was introduced in version 9.4.0.
|
||||
|
||||
## 9.7.3 (10/16/2024)
|
||||
### Behavior Changes
|
||||
* OPTIONS file to be loaded by remote worker is now preserved so that it does not get purged by the primary host. A similar technique as how we are preserving new SST files from getting purged is used for this. min_options_file_numbers_ is tracked like pending_outputs_ is tracked.
|
||||
|
||||
## 9.7.2 (10/08/2024)
|
||||
### Bug Fixes
|
||||
* Fix a bug for surfacing write unix time: `Iterator::GetProperty("rocksdb.iterator.write-time")` for non-L0 files.
|
||||
|
||||
## 9.7.1 (09/26/2024)
|
||||
### Bug Fixes
|
||||
* Several DB option settings could be lost through `GetOptionsFromString()`, possibly elsewhere as well. Affected options, now fixed:`background_close_inactive_wals`, `write_dbid_to_manifest`, `write_identity_file`, `prefix_seek_opt_in_only`
|
||||
* Fix under counting of allocated memory in the compressed secondary cache due to looking at the compressed block size rather than the actual memory allocated, which could be larger due to internal fragmentation.
|
||||
* Skip insertion of compressed blocks in the secondary cache if the lowest_used_cache_tier DB option is kVolatileTier.
|
||||
|
||||
## 9.7.0 (09/20/2024)
|
||||
### New Features
|
||||
* Make Cache a customizable class that can be instantiated by the object registry.
|
||||
* Add new option `prefix_seek_opt_in_only` that makes iterators generally safer when you might set a `prefix_extractor`. When `prefix_seek_opt_in_only=true`, which is expected to be the future default, prefix seek is only used when `prefix_same_as_start` or `auto_prefix_mode` are set. Also, `prefix_same_as_start` and `auto_prefix_mode` now allow prefix filtering even with `total_order_seek=true`.
|
||||
* Add a new table property "rocksdb.key.largest.seqno" which records the largest sequence number of all keys in file. It is verified to be zero during SST file ingestion.
|
||||
|
||||
### Behavior Changes
|
||||
* Changed the semantics of the BlobDB configuration option `blob_garbage_collection_force_threshold` to define a threshold for the overall garbage ratio of all blob files currently eligible for garbage collection (according to `blob_garbage_collection_age_cutoff`). This can provide better control over space amplification at the cost of slightly higher write amplification.
|
||||
* Set `write_dbid_to_manifest=true` by default. This means DB ID will now be preserved through backups, checkpoints, etc. by default. Also add `write_identity_file` option which can be set to false for anticipated future behavior.
|
||||
* In FIFO compaction, compactions for changing file temperature (configured by option `file_temperature_age_thresholds`) will compact one file at a time, instead of merging multiple eligible file together (#13018).
|
||||
* Support ingesting db generated files using hard link, i.e. IngestExternalFileOptions::move_files/link_files and IngestExternalFileOptions::allow_db_generated_files.
|
||||
* Add a new file ingestion option `IngestExternalFileOptions::link_files` to hard link input files and preserve original files links after ingestion.
|
||||
* DB::Close now untracks files in SstFileManager, making avaialble any space used
|
||||
by them. Prior to this change they would be orphaned until the DB is re-opened.
|
||||
|
||||
### Bug Fixes
|
||||
* Fix a bug in CompactRange() where result files may not be compacted in any future compaction. This can only happen when users configure CompactRangeOptions::change_level to true and the change level step of manual compaction fails (#13009).
|
||||
* Fix handling of dynamic change of `prefix_extractor` with memtable prefix filter. Previously, prefix seek could mix different prefix interpretations between memtable and SST files. Now the latest `prefix_extractor` at the time of iterator creation or refresh is respected.
|
||||
* Fix a bug with manual_wal_flush and auto error recovery from WAL failure that may cause CFs to be inconsistent (#12995). The fix will set potential WAL write failure as fatal error when manual_wal_flush is true, and disables auto error recovery from these errors.
|
||||
|
||||
## 9.6.0 (08/19/2024)
|
||||
### New Features
|
||||
* *Best efforts recovery supports recovering to incomplete Version with a clean seqno cut that presents a valid point in time view from the user's perspective, if versioning history doesn't include atomic flush.
|
||||
* New option `BlockBasedTableOptions::decouple_partitioned_filters` should improve efficiency in serving read queries because filter and index partitions can consistently target the configured `metadata_block_size`. This option is currently opt-in.
|
||||
* Introduce a new mutable CF option `paranoid_memory_checks`. It enables additional validation on data integrity during reads/scanning. Currently, skip list based memtable will validate key ordering during look up and scans.
|
||||
|
||||
### Public API Changes
|
||||
* Add ticker stats to count file read retries due to checksum mismatch
|
||||
* Adds optional installation callback function for remote compaction
|
||||
|
||||
### Behavior Changes
|
||||
* There may be less intra-L0 compaction triggered by total L0 size being too small. We now use compensated file size (tombstones are assigned some value size) when calculating L0 size and reduce the threshold for L0 size limit. This is to avoid accumulating too much data/tombstones in L0.
|
||||
|
||||
### Bug Fixes
|
||||
* *Make DestroyDB supports slow deletion when it's configured in `SstFileManager`. The slow deletion is subject to the configured `rate_bytes_per_sec`, but not subject to the `max_trash_db_ratio`.
|
||||
* Fixed a bug where we set unprep_seqs_ even when WriteImpl() fails. This was caught by stress test write fault injection in WriteImpl(). This may have incorrectly caused iteration creation failure for unvalidated writes or returned wrong result for WriteUnpreparedTxn::GetUnpreparedSequenceNumbers().
|
||||
* Fixed a bug where successful write right after error recovery for last failed write finishes causes duplicate WAL entries
|
||||
* Fixed a data race involving the background error status in `unordered_write` mode.
|
||||
* *Fix a bug where file snapshot functions like backup, checkpoint may attempt to copy a non-existing manifest file. #12882
|
||||
* Fix a bug where per kv checksum corruption may be ignored in MultiGet().
|
||||
* Fix a race condition in pessimistic transactions that could allow multiple transactions with the same name to be registered simultaneously, resulting in a crash or other unpredictable behavior.
|
||||
|
||||
## 9.5.0 (07/19/2024)
|
||||
### Public API Changes
|
||||
* Introduced new C API function rocksdb_writebatch_iterate_cf for column family-aware iteration over the contents of a WriteBatch
|
||||
|
||||
@@ -630,6 +630,11 @@ VALGRIND_VER := $(join $(VALGRIND_VER),valgrind)
|
||||
VALGRIND_OPTS = --error-exitcode=$(VALGRIND_ERROR) --leak-check=full
|
||||
# Not yet supported: --show-leak-kinds=definite,possible,reachable --errors-for-leak-kinds=definite,possible,reachable
|
||||
|
||||
# Work around valgrind hanging on systems with limited internet access
|
||||
ifneq ($(shell which git 2>/dev/null && git config --get https.proxy),)
|
||||
export DEBUGINFOD_URLS=
|
||||
endif
|
||||
|
||||
TEST_OBJECTS = $(patsubst %.cc, $(OBJ_DIR)/%.o, $(TEST_LIB_SOURCES) $(MOCK_LIB_SOURCES)) $(GTEST)
|
||||
BENCH_OBJECTS = $(patsubst %.cc, $(OBJ_DIR)/%.o, $(BENCH_LIB_SOURCES))
|
||||
CACHE_BENCH_OBJECTS = $(patsubst %.cc, $(OBJ_DIR)/%.o, $(CACHE_BENCH_LIB_SOURCES))
|
||||
@@ -1164,16 +1169,16 @@ ubsan_crash_test_with_best_efforts_recovery: clean
|
||||
$(MAKE) clean
|
||||
|
||||
full_valgrind_test:
|
||||
ROCKSDB_FULL_VALGRIND_RUN=1 DISABLE_JEMALLOC=1 $(MAKE) valgrind_check
|
||||
ROCKSDB_FULL_VALGRIND_RUN=1 DISABLE_JEMALLOC=1 PORTABLE=1 $(MAKE) valgrind_check
|
||||
|
||||
full_valgrind_test_some:
|
||||
ROCKSDB_FULL_VALGRIND_RUN=1 DISABLE_JEMALLOC=1 $(MAKE) valgrind_check_some
|
||||
ROCKSDB_FULL_VALGRIND_RUN=1 DISABLE_JEMALLOC=1 PORTABLE=1 $(MAKE) valgrind_check_some
|
||||
|
||||
valgrind_test:
|
||||
ROCKSDB_VALGRIND_RUN=1 DISABLE_JEMALLOC=1 $(MAKE) valgrind_check
|
||||
ROCKSDB_VALGRIND_RUN=1 DISABLE_JEMALLOC=1 PORTABLE=1 $(MAKE) valgrind_check
|
||||
|
||||
valgrind_test_some:
|
||||
ROCKSDB_VALGRIND_RUN=1 DISABLE_JEMALLOC=1 $(MAKE) valgrind_check_some
|
||||
ROCKSDB_VALGRIND_RUN=1 DISABLE_JEMALLOC=1 PORTABLE=1 $(MAKE) valgrind_check_some
|
||||
|
||||
valgrind_check: $(TESTS)
|
||||
$(MAKE) DRIVER="$(VALGRIND_VER) $(VALGRIND_OPTS)" gen_parallel_tests
|
||||
@@ -2484,7 +2489,7 @@ checkout_folly:
|
||||
fi
|
||||
@# Pin to a particular version for public CI, so that PR authors don't
|
||||
@# need to worry about folly breaking our integration. Update periodically
|
||||
cd third-party/folly && git reset --hard c48fdd205c1c291651749d532b8055fe822bba25
|
||||
cd third-party/folly && git reset --hard 03041f014b6e6ebb6119ffae8b7a37308f52e913
|
||||
@# NOTE: this hack is required for clang in some cases
|
||||
perl -pi -e 's/int rv = syscall/int rv = (int)syscall/' third-party/folly/folly/detail/Futex.cpp
|
||||
@# NOTE: this hack is required for gcc in some cases
|
||||
|
||||
@@ -362,9 +362,9 @@ cpp_library_wrapper(name="rocksdb_lib", srcs=[
|
||||
"//folly/experimental/coro:coroutine",
|
||||
"//folly/experimental/coro:task",
|
||||
"//folly/synchronization:distributed_mutex",
|
||||
], headers=None, link_whole=False, extra_test_libs=False)
|
||||
], headers=glob(["**/*.h"]), link_whole=False, extra_test_libs=False)
|
||||
|
||||
cpp_library_wrapper(name="rocksdb_whole_archive_lib", srcs=[], deps=[":rocksdb_lib"], headers=None, link_whole=True, extra_test_libs=False)
|
||||
cpp_library_wrapper(name="rocksdb_whole_archive_lib", srcs=[], deps=[":rocksdb_lib"], headers=[], link_whole=True, extra_test_libs=False)
|
||||
|
||||
cpp_library_wrapper(name="rocksdb_test_lib", srcs=[
|
||||
"db/db_test_util.cc",
|
||||
@@ -378,7 +378,7 @@ cpp_library_wrapper(name="rocksdb_test_lib", srcs=[
|
||||
"tools/trace_analyzer_tool.cc",
|
||||
"utilities/agg_merge/test_agg_merge.cc",
|
||||
"utilities/cassandra/test_utils.cc",
|
||||
], deps=[":rocksdb_lib"], headers=None, link_whole=False, extra_test_libs=True)
|
||||
], deps=[":rocksdb_lib"], headers=[], link_whole=False, extra_test_libs=True)
|
||||
|
||||
cpp_library_wrapper(name="rocksdb_tools_lib", srcs=[
|
||||
"test_util/testutil.cc",
|
||||
@@ -386,9 +386,9 @@ cpp_library_wrapper(name="rocksdb_tools_lib", srcs=[
|
||||
"tools/db_bench_tool.cc",
|
||||
"tools/simulated_hybrid_file_system.cc",
|
||||
"tools/trace_analyzer_tool.cc",
|
||||
], deps=[":rocksdb_lib"], headers=None, link_whole=False, extra_test_libs=False)
|
||||
], deps=[":rocksdb_lib"], headers=[], link_whole=False, extra_test_libs=False)
|
||||
|
||||
cpp_library_wrapper(name="rocksdb_cache_bench_tools_lib", srcs=["cache/cache_bench_tool.cc"], deps=[":rocksdb_lib"], headers=None, 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)
|
||||
|
||||
rocks_cpp_library_wrapper(name="rocksdb_stress_lib", srcs=[
|
||||
"db_stress_tool/batched_ops_stress.cc",
|
||||
@@ -410,13 +410,15 @@ rocks_cpp_library_wrapper(name="rocksdb_stress_lib", srcs=[
|
||||
"test_util/testutil.cc",
|
||||
"tools/block_cache_analyzer/block_cache_trace_analyzer.cc",
|
||||
"tools/trace_analyzer_tool.cc",
|
||||
], headers=None)
|
||||
], headers=[])
|
||||
|
||||
|
||||
cpp_binary_wrapper(name="ldb", srcs=["tools/ldb.cc"], deps=[":rocksdb_tools_lib"], extra_preprocessor_flags=[], extra_bench_libs=False)
|
||||
|
||||
cpp_binary_wrapper(name="db_stress", srcs=["db_stress_tool/db_stress.cc"], deps=[":rocksdb_stress_lib"], extra_preprocessor_flags=[], extra_bench_libs=False)
|
||||
|
||||
cpp_binary_wrapper(name="db_bench", srcs=["tools/db_bench.cc"], deps=[":rocksdb_tools_lib"], extra_preprocessor_flags=[], extra_bench_libs=False)
|
||||
|
||||
cpp_binary_wrapper(name="cache_bench", srcs=["cache/cache_bench.cc"], deps=[":rocksdb_cache_bench_tools_lib"], extra_preprocessor_flags=[], extra_bench_libs=False)
|
||||
|
||||
cpp_binary_wrapper(name="ribbon_bench", srcs=["microbench/ribbon_bench.cc"], deps=[], extra_preprocessor_flags=[], extra_bench_libs=True)
|
||||
@@ -5024,7 +5026,7 @@ cpp_unittest_wrapper(name="dynamic_bloom_test",
|
||||
extra_compiler_flags=[])
|
||||
|
||||
|
||||
cpp_library_wrapper(name="env_basic_test_lib", srcs=["env/env_basic_test.cc"], deps=[":rocksdb_test_lib"], headers=None, link_whole=False, extra_test_libs=True)
|
||||
cpp_library_wrapper(name="env_basic_test_lib", srcs=["env/env_basic_test.cc"], deps=[":rocksdb_test_lib"], headers=[], link_whole=False, extra_test_libs=True)
|
||||
|
||||
cpp_unittest_wrapper(name="env_basic_test",
|
||||
srcs=["env/env_basic_test.cc"],
|
||||
|
||||
@@ -11,7 +11,7 @@ import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
from targets_builder import TARGETSBuilder
|
||||
from targets_builder import TARGETSBuilder, LiteralValue
|
||||
|
||||
from util import ColorString
|
||||
|
||||
@@ -150,6 +150,7 @@ def generate_targets(repo_path, deps_map):
|
||||
"//folly/experimental/coro:task",
|
||||
"//folly/synchronization:distributed_mutex",
|
||||
],
|
||||
headers=LiteralValue("glob([\"**/*.h\"])")
|
||||
)
|
||||
# rocksdb_whole_archive_lib
|
||||
TARGETS.add_library(
|
||||
@@ -158,7 +159,6 @@ def generate_targets(repo_path, deps_map):
|
||||
deps=[
|
||||
":rocksdb_lib",
|
||||
],
|
||||
headers=None,
|
||||
extra_external_deps="",
|
||||
link_whole=True,
|
||||
)
|
||||
@@ -201,6 +201,10 @@ def generate_targets(repo_path, deps_map):
|
||||
TARGETS.add_binary(
|
||||
"db_stress", ["db_stress_tool/db_stress.cc"], [":rocksdb_stress_lib"]
|
||||
)
|
||||
# db_bench binary
|
||||
TARGETS.add_binary(
|
||||
"db_bench", ["tools/db_bench.cc"], [":rocksdb_tools_lib"]
|
||||
)
|
||||
# cache_bench binary
|
||||
TARGETS.add_binary(
|
||||
"cache_bench", ["cache/cache_bench.cc"], [":rocksdb_cache_bench_tools_lib"]
|
||||
|
||||
@@ -9,17 +9,28 @@ import pprint
|
||||
|
||||
import targets_cfg
|
||||
|
||||
class LiteralValue:
|
||||
def __init__(self, value):
|
||||
self.value = value
|
||||
|
||||
def __str__(self):
|
||||
return str(self.value)
|
||||
|
||||
def smart_quote_value(val):
|
||||
if isinstance(val, LiteralValue):
|
||||
return str(val)
|
||||
return '"%s"' % val
|
||||
|
||||
def pretty_list(lst, indent=8):
|
||||
if lst is None or len(lst) == 0:
|
||||
return ""
|
||||
|
||||
if len(lst) == 1:
|
||||
return '"%s"' % lst[0]
|
||||
return smart_quote_value(lst[0])
|
||||
|
||||
separator = '",\n%s"' % (" " * indent)
|
||||
res = separator.join(sorted(lst))
|
||||
res = "\n" + (" " * indent) + '"' + res + '",\n' + (" " * (indent - 4))
|
||||
separator = ',\n%s' % (" " * indent)
|
||||
res = separator.join(sorted(map(smart_quote_value, lst)))
|
||||
res = "\n" + (" " * indent) + res + ',\n' + (" " * (indent - 4))
|
||||
return res
|
||||
|
||||
|
||||
@@ -48,7 +59,12 @@ class TARGETSBuilder:
|
||||
extra_test_libs=False,
|
||||
):
|
||||
if headers is not None:
|
||||
headers = "[" + pretty_list(headers) + "]"
|
||||
if isinstance(headers, LiteralValue):
|
||||
headers = str(headers)
|
||||
else:
|
||||
headers = "[" + pretty_list(headers) + "]"
|
||||
else:
|
||||
headers = "[]"
|
||||
with open(self.path, "ab") as targets_file:
|
||||
targets_file.write(
|
||||
targets_cfg.library_template.format(
|
||||
@@ -65,8 +81,7 @@ class TARGETSBuilder:
|
||||
self.total_lib = self.total_lib + 1
|
||||
|
||||
def add_rocksdb_library(self, name, srcs, headers=None, external_dependencies=None):
|
||||
if headers is not None:
|
||||
headers = "[" + pretty_list(headers) + "]"
|
||||
headers = "[" + pretty_list(headers) + "]"
|
||||
with open(self.path, "ab") as targets_file:
|
||||
targets_file.write(
|
||||
targets_cfg.rocksdb_library_template.format(
|
||||
|
||||
Vendored
+16
-12
@@ -133,19 +133,23 @@ Status Cache::CreateFromString(const ConfigOptions& config_options,
|
||||
std::shared_ptr<Cache>* result) {
|
||||
Status status;
|
||||
std::shared_ptr<Cache> cache;
|
||||
if (value.find('=') == std::string::npos) {
|
||||
cache = NewLRUCache(ParseSizeT(value));
|
||||
} else {
|
||||
LRUCacheOptions cache_opts;
|
||||
status = OptionTypeInfo::ParseStruct(config_options, "",
|
||||
&lru_cache_options_type_info, "",
|
||||
value, &cache_opts);
|
||||
if (status.ok()) {
|
||||
cache = NewLRUCache(cache_opts);
|
||||
if (value.find("://") == std::string::npos) {
|
||||
if (value.find('=') == std::string::npos) {
|
||||
cache = NewLRUCache(ParseSizeT(value));
|
||||
} else {
|
||||
LRUCacheOptions cache_opts;
|
||||
status = OptionTypeInfo::ParseStruct(config_options, "",
|
||||
&lru_cache_options_type_info, "",
|
||||
value, &cache_opts);
|
||||
if (status.ok()) {
|
||||
cache = NewLRUCache(cache_opts);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (status.ok()) {
|
||||
result->swap(cache);
|
||||
if (status.ok()) {
|
||||
result->swap(cache);
|
||||
}
|
||||
} else {
|
||||
status = LoadSharedObject<Cache>(config_options, value, result);
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
Vendored
+26
@@ -886,6 +886,32 @@ TEST_P(CacheTest, ApplyToAllEntriesDuringResize) {
|
||||
ASSERT_EQ(special_count, kSpecialCount);
|
||||
}
|
||||
|
||||
TEST_P(CacheTest, ApplyToHandleTest) {
|
||||
std::string callback_state;
|
||||
const auto callback = [&](const Slice& key, Cache::ObjectPtr value,
|
||||
size_t charge,
|
||||
const Cache::CacheItemHelper* helper) {
|
||||
callback_state = std::to_string(DecodeKey(key)) + "," +
|
||||
std::to_string(DecodeValue(value)) + "," +
|
||||
std::to_string(charge);
|
||||
assert(helper == &CacheTest::kHelper);
|
||||
};
|
||||
|
||||
std::vector<std::string> inserted;
|
||||
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
Insert(i, i * 2, i + 1);
|
||||
inserted.push_back(std::to_string(i) + "," + std::to_string(i * 2) + "," +
|
||||
std::to_string(i + 1));
|
||||
}
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
Cache::Handle* handle = cache_->Lookup(EncodeKey(i));
|
||||
cache_->ApplyToHandle(cache_.get(), handle, callback);
|
||||
EXPECT_EQ(inserted[i], callback_state);
|
||||
cache_->Release(handle);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(CacheTest, DefaultShardBits) {
|
||||
// Prevent excessive allocation (to save time & space)
|
||||
estimated_value_size_ = 100000;
|
||||
|
||||
Vendored
+16
@@ -1444,6 +1444,22 @@ const Cache::CacheItemHelper* BaseHyperClockCache<Table>::GetCacheItemHelper(
|
||||
return h->helper;
|
||||
}
|
||||
|
||||
template <class Table>
|
||||
void BaseHyperClockCache<Table>::ApplyToHandle(
|
||||
Cache* cache, Handle* handle,
|
||||
const std::function<void(const Slice& key, Cache::ObjectPtr value,
|
||||
size_t charge, const CacheItemHelper* helper)>&
|
||||
callback) {
|
||||
BaseHyperClockCache<Table>* cache_ptr =
|
||||
static_cast<BaseHyperClockCache<Table>*>(cache);
|
||||
auto h = static_cast<const typename Table::HandleImpl*>(handle);
|
||||
UniqueId64x2 unhashed;
|
||||
auto hash_seed = cache_ptr->GetShard(h->GetHash()).GetTable().GetHashSeed();
|
||||
callback(
|
||||
ClockCacheShard<Table>::ReverseHash(h->hashed_key, &unhashed, hash_seed),
|
||||
h->value, h->GetTotalCharge(), h->helper);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
// For each cache shard, estimate what the table load factor would be if
|
||||
|
||||
Vendored
+6
@@ -1128,6 +1128,12 @@ class BaseHyperClockCache : public ShardedCache<ClockCacheShard<Table>> {
|
||||
|
||||
const CacheItemHelper* GetCacheItemHelper(Handle* handle) const override;
|
||||
|
||||
void ApplyToHandle(
|
||||
Cache* cache, Handle* handle,
|
||||
const std::function<void(const Slice& key, Cache::ObjectPtr obj,
|
||||
size_t charge, const CacheItemHelper* helper)>&
|
||||
callback) override;
|
||||
|
||||
void ReportProblems(
|
||||
const std::shared_ptr<Logger>& /*info_log*/) const override;
|
||||
};
|
||||
|
||||
Vendored
+38
-8
@@ -79,7 +79,11 @@ std::unique_ptr<SecondaryCacheResultHandle> CompressedSecondaryCache::Lookup(
|
||||
data_ptr = GetVarint32Ptr(data_ptr, data_ptr + 1,
|
||||
static_cast<uint32_t*>(&source_32));
|
||||
source = static_cast<CacheTier>(source_32);
|
||||
handle_value_charge -= (data_ptr - ptr->get());
|
||||
uint64_t data_size = 0;
|
||||
data_ptr = GetVarint64Ptr(data_ptr, ptr->get() + handle_value_charge,
|
||||
static_cast<uint64_t*>(&data_size));
|
||||
assert(handle_value_charge > data_size);
|
||||
handle_value_charge = data_size;
|
||||
}
|
||||
MemoryAllocator* allocator = cache_options_.memory_allocator.get();
|
||||
|
||||
@@ -169,13 +173,15 @@ Status CompressedSecondaryCache::InsertInternal(
|
||||
}
|
||||
|
||||
auto internal_helper = GetHelper(cache_options_.enable_custom_split_merge);
|
||||
char header[10];
|
||||
char header[20];
|
||||
char* payload = header;
|
||||
payload = EncodeVarint32(payload, static_cast<uint32_t>(type));
|
||||
payload = EncodeVarint32(payload, static_cast<uint32_t>(source));
|
||||
size_t data_size = (*helper->size_cb)(value);
|
||||
char* data_size_ptr = payload;
|
||||
payload = EncodeVarint64(payload, data_size);
|
||||
|
||||
size_t header_size = payload - header;
|
||||
size_t data_size = (*helper->size_cb)(value);
|
||||
size_t total_size = data_size + header_size;
|
||||
CacheAllocationPtr ptr =
|
||||
AllocateBlock(total_size, cache_options_.memory_allocator.get());
|
||||
@@ -210,6 +216,8 @@ Status CompressedSecondaryCache::InsertInternal(
|
||||
|
||||
val = Slice(compressed_val);
|
||||
data_size = compressed_val.size();
|
||||
payload = EncodeVarint64(data_size_ptr, data_size);
|
||||
header_size = payload - header;
|
||||
total_size = header_size + data_size;
|
||||
PERF_COUNTER_ADD(compressed_sec_cache_compressed_bytes, data_size);
|
||||
|
||||
@@ -222,14 +230,21 @@ Status CompressedSecondaryCache::InsertInternal(
|
||||
|
||||
PERF_COUNTER_ADD(compressed_sec_cache_insert_real_count, 1);
|
||||
if (cache_options_.enable_custom_split_merge) {
|
||||
size_t charge{0};
|
||||
CacheValueChunk* value_chunks_head =
|
||||
SplitValueIntoChunks(val, cache_options_.compression_type, charge);
|
||||
return cache_->Insert(key, value_chunks_head, internal_helper, charge);
|
||||
size_t split_charge{0};
|
||||
CacheValueChunk* value_chunks_head = SplitValueIntoChunks(
|
||||
val, cache_options_.compression_type, split_charge);
|
||||
return cache_->Insert(key, value_chunks_head, internal_helper,
|
||||
split_charge);
|
||||
} else {
|
||||
#ifdef ROCKSDB_MALLOC_USABLE_SIZE
|
||||
size_t charge = malloc_usable_size(ptr.get());
|
||||
#else
|
||||
size_t charge = total_size;
|
||||
#endif
|
||||
std::memcpy(ptr.get(), header, header_size);
|
||||
CacheAllocationPtr* buf = new CacheAllocationPtr(std::move(ptr));
|
||||
return cache_->Insert(key, buf, internal_helper, total_size);
|
||||
charge += sizeof(CacheAllocationPtr);
|
||||
return cache_->Insert(key, buf, internal_helper, charge);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -398,6 +413,21 @@ const Cache::CacheItemHelper* CompressedSecondaryCache::GetHelper(
|
||||
}
|
||||
}
|
||||
|
||||
size_t CompressedSecondaryCache::TEST_GetCharge(const Slice& key) {
|
||||
Cache::Handle* lru_handle = cache_->Lookup(key);
|
||||
if (lru_handle == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t charge = cache_->GetCharge(lru_handle);
|
||||
if (cache_->Value(lru_handle) != nullptr &&
|
||||
!cache_options_.enable_custom_split_merge) {
|
||||
charge -= 10;
|
||||
}
|
||||
cache_->Release(lru_handle, /*erase_if_last_ref=*/false);
|
||||
return charge;
|
||||
}
|
||||
|
||||
std::shared_ptr<SecondaryCache>
|
||||
CompressedSecondaryCacheOptions::MakeSharedSecondaryCache() const {
|
||||
return std::make_shared<CompressedSecondaryCache>(*this);
|
||||
|
||||
Vendored
+2
@@ -139,6 +139,8 @@ class CompressedSecondaryCache : public SecondaryCache {
|
||||
const Cache::CacheItemHelper* helper,
|
||||
CompressionType type, CacheTier source);
|
||||
|
||||
size_t TEST_GetCharge(const Slice& key);
|
||||
|
||||
// TODO: clean up to use cleaner interfaces in typed_cache.h
|
||||
const Cache::CacheItemHelper* GetHelper(bool enable_custom_split_merge) const;
|
||||
std::shared_ptr<Cache> cache_;
|
||||
|
||||
+4
@@ -39,6 +39,8 @@ class CompressedSecondaryCacheTestBase : public testing::Test,
|
||||
protected:
|
||||
void BasicTestHelper(std::shared_ptr<SecondaryCache> sec_cache,
|
||||
bool sec_cache_is_compressed) {
|
||||
CompressedSecondaryCache* comp_sec_cache =
|
||||
static_cast<CompressedSecondaryCache*>(sec_cache.get());
|
||||
get_perf_context()->Reset();
|
||||
bool kept_in_sec_cache{true};
|
||||
// Lookup an non-existent key.
|
||||
@@ -66,6 +68,8 @@ class CompressedSecondaryCacheTestBase : public testing::Test,
|
||||
ASSERT_OK(sec_cache->Insert(key1, &item1, GetHelper(), false));
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_insert_real_count, 1);
|
||||
|
||||
ASSERT_GT(comp_sec_cache->TEST_GetCharge(key1), 1000);
|
||||
|
||||
std::unique_ptr<SecondaryCacheResultHandle> handle1_2 =
|
||||
sec_cache->Lookup(key1, GetHelper(), this, true, /*advise_erase=*/true,
|
||||
/*stats=*/nullptr, kept_in_sec_cache);
|
||||
|
||||
Vendored
+11
@@ -677,6 +677,17 @@ const Cache::CacheItemHelper* LRUCache::GetCacheItemHelper(
|
||||
return h->helper;
|
||||
}
|
||||
|
||||
void LRUCache::ApplyToHandle(
|
||||
Cache* cache, Handle* handle,
|
||||
const std::function<void(const Slice& key, ObjectPtr value, size_t charge,
|
||||
const CacheItemHelper* helper)>& callback) {
|
||||
auto cache_ptr = static_cast<LRUCache*>(cache);
|
||||
auto h = static_cast<const LRUHandle*>(handle);
|
||||
callback(h->key(), h->value,
|
||||
h->GetCharge(cache_ptr->GetShard(0).metadata_charge_policy_),
|
||||
h->helper);
|
||||
}
|
||||
|
||||
size_t LRUCache::TEST_GetLRUSize() {
|
||||
return SumOverShards([](LRUCacheShard& cs) { return cs.TEST_GetLRUSize(); });
|
||||
}
|
||||
|
||||
Vendored
+6
@@ -452,6 +452,12 @@ class LRUCache
|
||||
size_t GetCharge(Handle* handle) const override;
|
||||
const CacheItemHelper* GetCacheItemHelper(Handle* handle) const override;
|
||||
|
||||
void ApplyToHandle(
|
||||
Cache* cache, Handle* handle,
|
||||
const std::function<void(const Slice& key, ObjectPtr obj, size_t charge,
|
||||
const CacheItemHelper* helper)>& callback)
|
||||
override;
|
||||
|
||||
// Retrieves number of elements in LRU, for unit test purpose only.
|
||||
size_t TEST_GetLRUSize();
|
||||
// Retrieves high pri pool ratio.
|
||||
|
||||
Vendored
+2
-1
@@ -271,7 +271,8 @@ Status CacheWithSecondaryAdapter::Insert(const Slice& key, ObjectPtr value,
|
||||
// Warm up the secondary cache with the compressed block. The secondary
|
||||
// cache may choose to ignore it based on the admission policy.
|
||||
if (value != nullptr && !compressed_value.empty() &&
|
||||
adm_policy_ == TieredAdmissionPolicy::kAdmPolicyThreeQueue) {
|
||||
adm_policy_ == TieredAdmissionPolicy::kAdmPolicyThreeQueue &&
|
||||
helper->IsSecondaryCacheCompatible()) {
|
||||
Status status = secondary_cache_->InsertSaved(key, compressed_value, type);
|
||||
assert(status.ok() || status.IsNotSupported());
|
||||
}
|
||||
|
||||
Vendored
+56
@@ -253,6 +253,7 @@ TEST_F(DBTieredSecondaryCacheTest, BasicTest) {
|
||||
table_options.cache_index_and_filter_blocks = false;
|
||||
Options options = GetDefaultOptions();
|
||||
options.create_if_missing = true;
|
||||
options.compression = kLZ4Compression;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
|
||||
// Disable paranoid_file_checks so that flush will not read back the newly
|
||||
@@ -364,6 +365,7 @@ TEST_F(DBTieredSecondaryCacheTest, BasicMultiGetTest) {
|
||||
table_options.cache_index_and_filter_blocks = false;
|
||||
Options options = GetDefaultOptions();
|
||||
options.create_if_missing = true;
|
||||
options.compression = kLZ4Compression;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
|
||||
options.paranoid_file_checks = false;
|
||||
@@ -506,6 +508,7 @@ TEST_F(DBTieredSecondaryCacheTest, WaitAllTest) {
|
||||
table_options.cache_index_and_filter_blocks = false;
|
||||
Options options = GetDefaultOptions();
|
||||
options.create_if_missing = true;
|
||||
options.compression = kLZ4Compression;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
|
||||
options.paranoid_file_checks = false;
|
||||
@@ -606,6 +609,7 @@ TEST_F(DBTieredSecondaryCacheTest, ReadyBeforeWaitAllTest) {
|
||||
table_options.cache_index_and_filter_blocks = false;
|
||||
Options options = GetDefaultOptions();
|
||||
options.create_if_missing = true;
|
||||
options.compression = kLZ4Compression;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
options.statistics = CreateDBStatistics();
|
||||
|
||||
@@ -717,6 +721,7 @@ TEST_F(DBTieredSecondaryCacheTest, IterateTest) {
|
||||
table_options.cache_index_and_filter_blocks = false;
|
||||
Options options = GetDefaultOptions();
|
||||
options.create_if_missing = true;
|
||||
options.compression = kLZ4Compression;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
|
||||
options.paranoid_file_checks = false;
|
||||
@@ -760,6 +765,54 @@ TEST_F(DBTieredSecondaryCacheTest, IterateTest) {
|
||||
Destroy(options);
|
||||
}
|
||||
|
||||
TEST_F(DBTieredSecondaryCacheTest, VolatileTierTest) {
|
||||
if (!LZ4_Supported()) {
|
||||
ROCKSDB_GTEST_SKIP("This test requires LZ4 support.");
|
||||
return;
|
||||
}
|
||||
|
||||
BlockBasedTableOptions table_options;
|
||||
// We want a block cache of size 5KB, and a compressed secondary cache of
|
||||
// size 5KB. However, we specify a block cache size of 256KB here in order
|
||||
// to take into account the cache reservation in the block cache on
|
||||
// behalf of the compressed cache. The unit of cache reservation is 256KB.
|
||||
// The effective block cache capacity will be calculated as 256 + 5 = 261KB,
|
||||
// and 256KB will be reserved for the compressed cache, leaving 5KB for
|
||||
// the primary block cache. We only have to worry about this here because
|
||||
// the cache size is so small.
|
||||
table_options.block_cache = NewCache(256 * 1024, 5 * 1024, 256 * 1024);
|
||||
table_options.block_size = 4 * 1024;
|
||||
table_options.cache_index_and_filter_blocks = false;
|
||||
Options options = GetDefaultOptions();
|
||||
options.create_if_missing = true;
|
||||
options.compression = kLZ4Compression;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
|
||||
// Disable paranoid_file_checks so that flush will not read back the newly
|
||||
// written file
|
||||
options.paranoid_file_checks = false;
|
||||
options.lowest_used_cache_tier = CacheTier::kVolatileTier;
|
||||
DestroyAndReopen(options);
|
||||
Random rnd(301);
|
||||
const int N = 256;
|
||||
for (int i = 0; i < N; i++) {
|
||||
std::string p_v;
|
||||
test::CompressibleString(&rnd, 0.5, 1007, &p_v);
|
||||
ASSERT_OK(Put(Key(i), p_v));
|
||||
}
|
||||
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
// Since lowest_used_cache_tier is the volatile tier, nothing should be
|
||||
// inserted in the secondary cache.
|
||||
std::string v = Get(Key(0));
|
||||
ASSERT_EQ(1007, v.size());
|
||||
ASSERT_EQ(nvm_sec_cache()->num_insert_saved(), 0u);
|
||||
ASSERT_EQ(nvm_sec_cache()->num_misses(), 0u);
|
||||
|
||||
Destroy(options);
|
||||
}
|
||||
|
||||
class DBTieredAdmPolicyTest
|
||||
: public DBTieredSecondaryCacheTest,
|
||||
public testing::WithParamInterface<TieredAdmissionPolicy> {};
|
||||
@@ -784,6 +837,7 @@ TEST_P(DBTieredAdmPolicyTest, CompressedOnlyTest) {
|
||||
table_options.cache_index_and_filter_blocks = false;
|
||||
Options options = GetDefaultOptions();
|
||||
options.create_if_missing = true;
|
||||
options.compression = kLZ4Compression;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
|
||||
size_t comp_cache_usage = compressed_secondary_cache()->TEST_GetUsage();
|
||||
@@ -836,6 +890,7 @@ TEST_P(DBTieredAdmPolicyTest, CompressedCacheAdmission) {
|
||||
table_options.cache_index_and_filter_blocks = false;
|
||||
Options options = GetDefaultOptions();
|
||||
options.create_if_missing = true;
|
||||
options.compression = kLZ4Compression;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
|
||||
size_t comp_cache_usage = compressed_secondary_cache()->TEST_GetUsage();
|
||||
@@ -937,6 +992,7 @@ TEST_F(DBTieredSecondaryCacheTest, FSBufferTest) {
|
||||
table_options.cache_index_and_filter_blocks = false;
|
||||
Options options = GetDefaultOptions();
|
||||
options.create_if_missing = true;
|
||||
options.compression = kLZ4Compression;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
options.statistics = CreateDBStatistics();
|
||||
options.env = wrap_env.get();
|
||||
|
||||
@@ -45,20 +45,23 @@ void ArenaWrappedDBIter::Init(
|
||||
const SequenceNumber& sequence, uint64_t max_sequential_skip_in_iteration,
|
||||
uint64_t version_number, ReadCallback* read_callback,
|
||||
ColumnFamilyHandleImpl* cfh, bool expose_blob_index, bool allow_refresh) {
|
||||
auto mem = arena_.AllocateAligned(sizeof(DBIter));
|
||||
db_iter_ = new (mem) DBIter(
|
||||
env, read_options, ioptions, mutable_cf_options, ioptions.user_comparator,
|
||||
/* iter */ nullptr, version, sequence, true,
|
||||
max_sequential_skip_in_iteration, read_callback, cfh, expose_blob_index);
|
||||
sv_number_ = version_number;
|
||||
read_options_ = read_options;
|
||||
allow_refresh_ = allow_refresh;
|
||||
memtable_range_tombstone_iter_ = nullptr;
|
||||
|
||||
if (!CheckFSFeatureSupport(env->GetFileSystem().get(),
|
||||
FSSupportedOps::kAsyncIO)) {
|
||||
read_options_.async_io = false;
|
||||
}
|
||||
read_options_.total_order_seek |= ioptions.prefix_seek_opt_in_only;
|
||||
|
||||
auto mem = arena_.AllocateAligned(sizeof(DBIter));
|
||||
db_iter_ = new (mem) DBIter(env, read_options_, ioptions, mutable_cf_options,
|
||||
ioptions.user_comparator,
|
||||
/* iter */ nullptr, version, sequence, true,
|
||||
max_sequential_skip_in_iteration, read_callback,
|
||||
cfh, expose_blob_index);
|
||||
|
||||
sv_number_ = version_number;
|
||||
allow_refresh_ = allow_refresh;
|
||||
memtable_range_tombstone_iter_ = nullptr;
|
||||
}
|
||||
|
||||
Status ArenaWrappedDBIter::Refresh() { return Refresh(nullptr); }
|
||||
|
||||
@@ -42,6 +42,7 @@ Status BlobFileCache::GetBlobFileReader(
|
||||
assert(blob_file_reader);
|
||||
assert(blob_file_reader->IsEmpty());
|
||||
|
||||
// NOTE: sharing same Cache with table_cache
|
||||
const Slice key = GetSliceForKey(&blob_file_number);
|
||||
|
||||
assert(cache_);
|
||||
@@ -98,4 +99,13 @@ Status BlobFileCache::GetBlobFileReader(
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
void BlobFileCache::Evict(uint64_t blob_file_number) {
|
||||
// NOTE: sharing same Cache with table_cache
|
||||
const Slice key = GetSliceForKey(&blob_file_number);
|
||||
|
||||
assert(cache_);
|
||||
|
||||
cache_.get()->Erase(key);
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -36,6 +36,15 @@ class BlobFileCache {
|
||||
uint64_t blob_file_number,
|
||||
CacheHandleGuard<BlobFileReader>* blob_file_reader);
|
||||
|
||||
// Called when a blob file is obsolete to ensure it is removed from the cache
|
||||
// to avoid effectively leaking the open file and assicated memory
|
||||
void Evict(uint64_t blob_file_number);
|
||||
|
||||
// Used to identify cache entries for blob files (not normally useful)
|
||||
static const Cache::CacheItemHelper* GetHelper() {
|
||||
return CacheInterface::GetBasicHelper();
|
||||
}
|
||||
|
||||
private:
|
||||
using CacheInterface =
|
||||
BasicTypedCacheInterface<BlobFileReader, CacheEntryRole::kMisc>;
|
||||
|
||||
@@ -4075,6 +4075,15 @@ void rocksdb_options_set_write_dbid_to_manifest(
|
||||
opt->rep.write_dbid_to_manifest = write_dbid_to_manifest;
|
||||
}
|
||||
|
||||
unsigned char rocksdb_options_get_write_identity_file(rocksdb_options_t* opt) {
|
||||
return opt->rep.write_identity_file;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_write_identity_file(
|
||||
rocksdb_options_t* opt, unsigned char write_identity_file) {
|
||||
opt->rep.write_identity_file = write_identity_file;
|
||||
}
|
||||
|
||||
unsigned char rocksdb_options_get_track_and_verify_wals_in_manifest(
|
||||
rocksdb_options_t* opt) {
|
||||
return opt->rep.track_and_verify_wals_in_manifest;
|
||||
|
||||
+12
-1
@@ -772,6 +772,8 @@ int main(int argc, char** argv) {
|
||||
rocksdb_options_set_write_buffer_size(options, 100000);
|
||||
rocksdb_options_set_paranoid_checks(options, 1);
|
||||
rocksdb_options_set_max_open_files(options, 10);
|
||||
/* Compatibility with how test was written */
|
||||
rocksdb_options_set_write_dbid_to_manifest(options, 0);
|
||||
|
||||
table_options = rocksdb_block_based_options_create();
|
||||
rocksdb_block_based_options_set_block_cache(table_options, cache);
|
||||
@@ -962,15 +964,24 @@ int main(int argc, char** argv) {
|
||||
rocksdb_options_t* options_dbid_in_manifest = rocksdb_options_create();
|
||||
rocksdb_options_set_create_if_missing(options_dbid_in_manifest, 1);
|
||||
|
||||
rocksdb_options_set_write_dbid_to_manifest(options_dbid_in_manifest, false);
|
||||
unsigned char write_to_manifest =
|
||||
rocksdb_options_get_write_dbid_to_manifest(options_dbid_in_manifest);
|
||||
CheckCondition(!write_to_manifest);
|
||||
rocksdb_options_set_write_dbid_to_manifest(options_dbid_in_manifest, true);
|
||||
CheckCondition(!write_to_manifest);
|
||||
write_to_manifest =
|
||||
rocksdb_options_get_write_dbid_to_manifest(options_dbid_in_manifest);
|
||||
CheckCondition(write_to_manifest);
|
||||
|
||||
rocksdb_options_set_write_identity_file(options_dbid_in_manifest, true);
|
||||
unsigned char write_identity_file =
|
||||
rocksdb_options_get_write_identity_file(options_dbid_in_manifest);
|
||||
CheckCondition(write_identity_file);
|
||||
rocksdb_options_set_write_identity_file(options_dbid_in_manifest, false);
|
||||
write_identity_file =
|
||||
rocksdb_options_get_write_identity_file(options_dbid_in_manifest);
|
||||
CheckCondition(!write_identity_file);
|
||||
|
||||
db = rocksdb_open(options_dbid_in_manifest, dbbackupname, &err);
|
||||
CheckNoError(err);
|
||||
|
||||
|
||||
+6
-23
@@ -1201,8 +1201,10 @@ Status ColumnFamilyData::RangesOverlapWithMemtables(
|
||||
read_opts.total_order_seek = true;
|
||||
MergeIteratorBuilder merge_iter_builder(&internal_comparator_, &arena);
|
||||
merge_iter_builder.AddIterator(super_version->mem->NewIterator(
|
||||
read_opts, /*seqno_to_time_mapping=*/nullptr, &arena));
|
||||
read_opts, /*seqno_to_time_mapping=*/nullptr, &arena,
|
||||
/*prefix_extractor=*/nullptr));
|
||||
super_version->imm->AddIterators(read_opts, /*seqno_to_time_mapping=*/nullptr,
|
||||
/*prefix_extractor=*/nullptr,
|
||||
&merge_iter_builder,
|
||||
false /* add_range_tombstone_iter */);
|
||||
ScopedArenaPtr<InternalIterator> memtable_iter(merge_iter_builder.Finish());
|
||||
@@ -1565,28 +1567,6 @@ Status ColumnFamilyData::SetOptions(
|
||||
return s;
|
||||
}
|
||||
|
||||
// REQUIRES: DB mutex held
|
||||
Env::WriteLifeTimeHint ColumnFamilyData::CalculateSSTWriteHint(int level) {
|
||||
if (initial_cf_options_.compaction_style != kCompactionStyleLevel) {
|
||||
return Env::WLTH_NOT_SET;
|
||||
}
|
||||
if (level == 0) {
|
||||
return Env::WLTH_MEDIUM;
|
||||
}
|
||||
int base_level = current_->storage_info()->base_level();
|
||||
|
||||
// L1: medium, L2: long, ...
|
||||
if (level - base_level >= 2) {
|
||||
return Env::WLTH_EXTREME;
|
||||
} else if (level < base_level) {
|
||||
// There is no restriction which prevents level passed in to be smaller
|
||||
// than base_level.
|
||||
return Env::WLTH_MEDIUM;
|
||||
}
|
||||
return static_cast<Env::WriteLifeTimeHint>(
|
||||
level - base_level + static_cast<int>(Env::WLTH_MEDIUM));
|
||||
}
|
||||
|
||||
Status ColumnFamilyData::AddDirectories(
|
||||
std::map<std::string, std::shared_ptr<FSDirectory>>* created_dirs) {
|
||||
Status s;
|
||||
@@ -1652,6 +1632,9 @@ bool ColumnFamilyData::ShouldPostponeFlushToRetainUDT(
|
||||
}
|
||||
for (const Slice& table_newest_udt :
|
||||
imm()->GetTablesNewestUDT(max_memtable_id)) {
|
||||
if (table_newest_udt.empty()) {
|
||||
continue;
|
||||
}
|
||||
assert(table_newest_udt.size() == full_history_ts_low.size());
|
||||
// Checking the newest UDT contained in MemTable with ascending ID up to
|
||||
// `max_memtable_id`. Return immediately on finding the first MemTable that
|
||||
|
||||
+1
-2
@@ -401,6 +401,7 @@ class ColumnFamilyData {
|
||||
SequenceNumber earliest_seq);
|
||||
|
||||
TableCache* table_cache() const { return table_cache_.get(); }
|
||||
BlobFileCache* blob_file_cache() const { return blob_file_cache_.get(); }
|
||||
BlobSource* blob_source() const { return blob_source_.get(); }
|
||||
|
||||
// See documentation in compaction_picker.h
|
||||
@@ -511,8 +512,6 @@ class ColumnFamilyData {
|
||||
return initial_cf_options_;
|
||||
}
|
||||
|
||||
Env::WriteLifeTimeHint CalculateSSTWriteHint(int level);
|
||||
|
||||
// created_dirs remembers directory created, so that we don't need to call
|
||||
// the same data creation operation again.
|
||||
Status AddDirectories(
|
||||
|
||||
@@ -686,12 +686,11 @@ bool Compaction::KeyRangeNotExistsBeyondOutputLevel(
|
||||
};
|
||||
|
||||
// Mark (or clear) each file that is being compacted
|
||||
void Compaction::MarkFilesBeingCompacted(bool mark_as_compacted) {
|
||||
void Compaction::MarkFilesBeingCompacted(bool being_compacted) const {
|
||||
for (size_t i = 0; i < num_input_levels(); i++) {
|
||||
for (size_t j = 0; j < inputs_[i].size(); j++) {
|
||||
assert(mark_as_compacted ? !inputs_[i][j]->being_compacted
|
||||
: inputs_[i][j]->being_compacted);
|
||||
inputs_[i][j]->being_compacted = mark_as_compacted;
|
||||
assert(being_compacted != inputs_[i][j]->being_compacted);
|
||||
inputs_[i][j]->being_compacted = being_compacted;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -735,7 +734,7 @@ uint64_t Compaction::CalculateTotalInputSize() const {
|
||||
return size;
|
||||
}
|
||||
|
||||
void Compaction::ReleaseCompactionFiles(Status status) {
|
||||
void Compaction::ReleaseCompactionFiles(const Status& status) {
|
||||
MarkFilesBeingCompacted(false);
|
||||
cfd_->compaction_picker()->ReleaseCompactionFiles(this, status);
|
||||
}
|
||||
|
||||
@@ -230,7 +230,7 @@ class Compaction {
|
||||
// Delete this compaction from the list of running compactions.
|
||||
//
|
||||
// Requirement: DB mutex held
|
||||
void ReleaseCompactionFiles(Status status);
|
||||
void ReleaseCompactionFiles(const Status& status);
|
||||
|
||||
// Returns the summary of the compaction in "output" with maximum "len"
|
||||
// in bytes. The caller is responsible for the memory management of
|
||||
@@ -435,13 +435,13 @@ class Compaction {
|
||||
const int start_level,
|
||||
const int output_level);
|
||||
|
||||
// mark (or clear) all files that are being compacted
|
||||
void MarkFilesBeingCompacted(bool being_compacted) const;
|
||||
|
||||
private:
|
||||
|
||||
Status InitInputTableProperties();
|
||||
|
||||
// mark (or clear) all files that are being compacted
|
||||
void MarkFilesBeingCompacted(bool mark_as_compacted);
|
||||
|
||||
// get the smallest and largest key present in files to be compacted
|
||||
static void GetBoundaryKeys(VersionStorageInfo* vstorage,
|
||||
const std::vector<CompactionInputFiles>& inputs,
|
||||
|
||||
@@ -251,12 +251,13 @@ void CompactionJob::Prepare() {
|
||||
|
||||
// Generate file_levels_ for compaction before making Iterator
|
||||
auto* c = compact_->compaction;
|
||||
ColumnFamilyData* cfd = c->column_family_data();
|
||||
[[maybe_unused]] ColumnFamilyData* cfd = c->column_family_data();
|
||||
assert(cfd != nullptr);
|
||||
assert(cfd->current()->storage_info()->NumLevelFiles(
|
||||
compact_->compaction->level()) > 0);
|
||||
const VersionStorageInfo* storage_info = c->input_version()->storage_info();
|
||||
assert(storage_info);
|
||||
assert(storage_info->NumLevelFiles(compact_->compaction->level()) > 0);
|
||||
|
||||
write_hint_ = cfd->CalculateSSTWriteHint(c->output_level());
|
||||
write_hint_ = storage_info->CalculateSSTWriteHint(c->output_level());
|
||||
bottommost_level_ = c->bottommost_level();
|
||||
|
||||
if (c->ShouldFormSubcompactions()) {
|
||||
@@ -297,8 +298,8 @@ void CompactionJob::Prepare() {
|
||||
for (const auto& each_level : *c->inputs()) {
|
||||
for (const auto& fmd : each_level.files) {
|
||||
std::shared_ptr<const TableProperties> tp;
|
||||
Status s =
|
||||
cfd->current()->GetTableProperties(read_options, &tp, fmd, nullptr);
|
||||
Status s = c->input_version()->GetTableProperties(read_options, &tp,
|
||||
fmd, nullptr);
|
||||
if (s.ok()) {
|
||||
s = seqno_to_time_mapping_.DecodeFrom(tp->seqno_to_time_mapping);
|
||||
}
|
||||
@@ -805,6 +806,12 @@ Status CompactionJob::Run() {
|
||||
}
|
||||
}
|
||||
|
||||
// Before the compaction starts, is_remote_compaction was set to true if
|
||||
// compaction_service is set. We now know whether each sub_compaction was
|
||||
// done remotely or not. Reset is_remote_compaction back to false and allow
|
||||
// AggregateCompactionStats() to set the right value.
|
||||
compaction_job_stats_->is_remote_compaction = false;
|
||||
|
||||
// Finish up all bookkeeping to unify the subcompaction results.
|
||||
compact_->AggregateCompactionStats(compaction_stats_, *compaction_job_stats_);
|
||||
uint64_t num_input_range_del = 0;
|
||||
@@ -1083,6 +1090,7 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
}
|
||||
// fallback to local compaction
|
||||
assert(comp_status == CompactionServiceJobStatus::kUseLocal);
|
||||
sub_compact->compaction_job_stats.is_remote_compaction = false;
|
||||
}
|
||||
|
||||
uint64_t prev_cpu_micros = db_options_.clock->CPUMicros();
|
||||
|
||||
@@ -209,6 +209,8 @@ class CompactionJob {
|
||||
// Returns true iff compaction_stats_.stats.num_input_records and
|
||||
// num_input_range_del are calculated successfully.
|
||||
bool UpdateCompactionStats(uint64_t* num_input_range_del = nullptr);
|
||||
virtual void UpdateCompactionJobStats(
|
||||
const InternalStats::CompactionStats& stats) const;
|
||||
void LogCompaction();
|
||||
virtual void RecordCompactionIOStats();
|
||||
void CleanupCompaction();
|
||||
@@ -279,8 +281,7 @@ class CompactionJob {
|
||||
bool* compaction_released);
|
||||
Status OpenCompactionOutputFile(SubcompactionState* sub_compact,
|
||||
CompactionOutputs& outputs);
|
||||
void UpdateCompactionJobStats(
|
||||
const InternalStats::CompactionStats& stats) const;
|
||||
|
||||
void RecordDroppedKeys(const CompactionIterationStats& c_iter_stats,
|
||||
CompactionJobStats* compaction_job_stats = nullptr);
|
||||
|
||||
@@ -377,9 +378,7 @@ class CompactionJob {
|
||||
// doesn't contain the LSM tree information, which is passed though MANIFEST
|
||||
// file.
|
||||
struct CompactionServiceInput {
|
||||
ColumnFamilyDescriptor column_family;
|
||||
|
||||
DBOptions db_options;
|
||||
std::string cf_name;
|
||||
|
||||
std::vector<SequenceNumber> snapshots;
|
||||
|
||||
@@ -398,13 +397,12 @@ struct CompactionServiceInput {
|
||||
bool has_end = false;
|
||||
std::string end;
|
||||
|
||||
uint64_t options_file_number;
|
||||
|
||||
// serialization interface to read and write the object
|
||||
static Status Read(const std::string& data_str, CompactionServiceInput* obj);
|
||||
Status Write(std::string* output);
|
||||
|
||||
// Initialize a dummy ColumnFamilyDescriptor
|
||||
CompactionServiceInput() : column_family("", ColumnFamilyOptions()) {}
|
||||
|
||||
#ifndef NDEBUG
|
||||
bool TEST_Equals(CompactionServiceInput* other);
|
||||
bool TEST_Equals(CompactionServiceInput* other, std::string* mismatch);
|
||||
@@ -418,19 +416,22 @@ struct CompactionServiceOutputFile {
|
||||
SequenceNumber largest_seqno;
|
||||
std::string smallest_internal_key;
|
||||
std::string largest_internal_key;
|
||||
uint64_t oldest_ancester_time;
|
||||
uint64_t file_creation_time;
|
||||
uint64_t epoch_number;
|
||||
uint64_t oldest_ancester_time = kUnknownOldestAncesterTime;
|
||||
uint64_t file_creation_time = kUnknownFileCreationTime;
|
||||
uint64_t epoch_number = kUnknownEpochNumber;
|
||||
std::string file_checksum = kUnknownFileChecksum;
|
||||
std::string file_checksum_func_name = kUnknownFileChecksumFuncName;
|
||||
uint64_t paranoid_hash;
|
||||
bool marked_for_compaction;
|
||||
UniqueId64x2 unique_id;
|
||||
UniqueId64x2 unique_id{};
|
||||
|
||||
CompactionServiceOutputFile() = default;
|
||||
CompactionServiceOutputFile(
|
||||
const std::string& name, SequenceNumber smallest, SequenceNumber largest,
|
||||
std::string _smallest_internal_key, std::string _largest_internal_key,
|
||||
uint64_t _oldest_ancester_time, uint64_t _file_creation_time,
|
||||
uint64_t _epoch_number, uint64_t _paranoid_hash,
|
||||
uint64_t _epoch_number, const std::string& _file_checksum,
|
||||
const std::string& _file_checksum_func_name, uint64_t _paranoid_hash,
|
||||
bool _marked_for_compaction, UniqueId64x2 _unique_id)
|
||||
: file_name(name),
|
||||
smallest_seqno(smallest),
|
||||
@@ -440,6 +441,8 @@ struct CompactionServiceOutputFile {
|
||||
oldest_ancester_time(_oldest_ancester_time),
|
||||
file_creation_time(_file_creation_time),
|
||||
epoch_number(_epoch_number),
|
||||
file_checksum(_file_checksum),
|
||||
file_checksum_func_name(_file_checksum_func_name),
|
||||
paranoid_hash(_paranoid_hash),
|
||||
marked_for_compaction(_marked_for_compaction),
|
||||
unique_id(std::move(_unique_id)) {}
|
||||
@@ -456,9 +459,6 @@ struct CompactionServiceResult {
|
||||
// location of the output files
|
||||
std::string output_path;
|
||||
|
||||
// some statistics about the compaction
|
||||
uint64_t num_output_records = 0;
|
||||
uint64_t total_bytes = 0;
|
||||
uint64_t bytes_read = 0;
|
||||
uint64_t bytes_written = 0;
|
||||
CompactionJobStats stats;
|
||||
@@ -504,6 +504,9 @@ class CompactionServiceCompactionJob : private CompactionJob {
|
||||
protected:
|
||||
void RecordCompactionIOStats() override;
|
||||
|
||||
void UpdateCompactionJobStats(
|
||||
const InternalStats::CompactionStats& stats) const override;
|
||||
|
||||
private:
|
||||
// Get table file name in output_path
|
||||
std::string GetTableFileName(uint64_t file_number) override;
|
||||
|
||||
@@ -50,7 +50,8 @@ void VerifyInitializationOfCompactionJobStats(
|
||||
ASSERT_EQ(compaction_job_stats.num_output_records, 0U);
|
||||
ASSERT_EQ(compaction_job_stats.num_output_files, 0U);
|
||||
|
||||
ASSERT_EQ(compaction_job_stats.is_manual_compaction, true);
|
||||
ASSERT_TRUE(compaction_job_stats.is_manual_compaction);
|
||||
ASSERT_FALSE(compaction_job_stats.is_remote_compaction);
|
||||
|
||||
ASSERT_EQ(compaction_job_stats.total_input_bytes, 0U);
|
||||
ASSERT_EQ(compaction_job_stats.total_output_bytes, 0U);
|
||||
@@ -545,14 +546,14 @@ class CompactionJobTestBase : public testing::Test {
|
||||
ASSERT_OK(s);
|
||||
db_options_.info_log = info_log;
|
||||
|
||||
versions_.reset(new VersionSet(
|
||||
dbname_, &db_options_, env_options_, table_cache_.get(),
|
||||
&write_buffer_manager_, &write_controller_,
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
|
||||
/*db_id=*/"", /*db_session_id=*/"", /*daily_offpeak_time_utc=*/"",
|
||||
/*error_handler=*/nullptr, /*read_only=*/false));
|
||||
versions_.reset(
|
||||
new VersionSet(dbname_, &db_options_, env_options_, table_cache_.get(),
|
||||
&write_buffer_manager_, &write_controller_,
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
|
||||
test::kUnitTestDbId, /*db_session_id=*/"",
|
||||
/*daily_offpeak_time_utc=*/"",
|
||||
/*error_handler=*/nullptr, /*read_only=*/false));
|
||||
compaction_job_stats_.Reset();
|
||||
ASSERT_OK(SetIdentityFile(WriteOptions(), env_, dbname_));
|
||||
|
||||
VersionEdit new_db;
|
||||
new_db.SetLogNumber(0);
|
||||
@@ -575,7 +576,8 @@ class CompactionJobTestBase : public testing::Test {
|
||||
}
|
||||
ASSERT_OK(s);
|
||||
// Make "CURRENT" file that points to the new manifest file.
|
||||
s = SetCurrentFile(WriteOptions(), fs_.get(), dbname_, 1, nullptr);
|
||||
s = SetCurrentFile(WriteOptions(), fs_.get(), dbname_, 1,
|
||||
Temperature::kUnknown, nullptr);
|
||||
|
||||
ASSERT_OK(s);
|
||||
|
||||
@@ -1567,17 +1569,7 @@ TEST_F(CompactionJobTest, InputSerialization) {
|
||||
const int kStrMaxLen = 1000;
|
||||
Random rnd(static_cast<uint32_t>(time(nullptr)));
|
||||
Random64 rnd64(time(nullptr));
|
||||
input.column_family.name = rnd.RandomString(rnd.Uniform(kStrMaxLen));
|
||||
input.column_family.options.comparator = ReverseBytewiseComparator();
|
||||
input.column_family.options.max_bytes_for_level_base =
|
||||
rnd64.Uniform(UINT64_MAX);
|
||||
input.column_family.options.disable_auto_compactions = rnd.OneIn(2);
|
||||
input.column_family.options.compression = kZSTD;
|
||||
input.column_family.options.compression_opts.level = 4;
|
||||
input.db_options.max_background_flushes = 10;
|
||||
input.db_options.paranoid_checks = rnd.OneIn(2);
|
||||
input.db_options.statistics = CreateDBStatistics();
|
||||
input.db_options.env = env_;
|
||||
input.cf_name = rnd.RandomString(rnd.Uniform(kStrMaxLen));
|
||||
while (!rnd.OneIn(10)) {
|
||||
input.snapshots.emplace_back(rnd64.Uniform(UINT64_MAX));
|
||||
}
|
||||
@@ -1605,10 +1597,10 @@ TEST_F(CompactionJobTest, InputSerialization) {
|
||||
ASSERT_TRUE(deserialized1.TEST_Equals(&input));
|
||||
|
||||
// Test mismatch
|
||||
deserialized1.db_options.max_background_flushes += 10;
|
||||
deserialized1.output_level += 10;
|
||||
std::string mismatch;
|
||||
ASSERT_FALSE(deserialized1.TEST_Equals(&input, &mismatch));
|
||||
ASSERT_EQ(mismatch, "db_options.max_background_flushes");
|
||||
ASSERT_EQ(mismatch, "output_level");
|
||||
|
||||
// Test unknown field
|
||||
CompactionServiceInput deserialized2;
|
||||
@@ -1664,20 +1656,30 @@ TEST_F(CompactionJobTest, ResultSerialization) {
|
||||
};
|
||||
result.status =
|
||||
status_list.at(rnd.Uniform(static_cast<int>(status_list.size())));
|
||||
|
||||
std::string file_checksum = rnd.RandomBinaryString(rnd.Uniform(kStrMaxLen));
|
||||
std::string file_checksum_func_name = "MyAwesomeChecksumGenerator";
|
||||
while (!rnd.OneIn(10)) {
|
||||
UniqueId64x2 id{rnd64.Uniform(UINT64_MAX), rnd64.Uniform(UINT64_MAX)};
|
||||
result.output_files.emplace_back(
|
||||
rnd.RandomString(rnd.Uniform(kStrMaxLen)), rnd64.Uniform(UINT64_MAX),
|
||||
rnd64.Uniform(UINT64_MAX),
|
||||
rnd.RandomBinaryString(rnd.Uniform(kStrMaxLen)),
|
||||
rnd.RandomBinaryString(rnd.Uniform(kStrMaxLen)),
|
||||
rnd64.Uniform(UINT64_MAX), rnd64.Uniform(UINT64_MAX),
|
||||
rnd64.Uniform(UINT64_MAX), rnd64.Uniform(UINT64_MAX), rnd.OneIn(2), id);
|
||||
rnd.RandomString(rnd.Uniform(kStrMaxLen)) /* file_name */,
|
||||
rnd64.Uniform(UINT64_MAX) /* smallest_seqno */,
|
||||
rnd64.Uniform(UINT64_MAX) /* largest_seqno */,
|
||||
rnd.RandomBinaryString(
|
||||
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) /* file_creation_time */,
|
||||
rnd64.Uniform(UINT64_MAX) /* epoch_number */,
|
||||
file_checksum /* file_checksum */,
|
||||
file_checksum_func_name /* file_checksum_func_name */,
|
||||
rnd64.Uniform(UINT64_MAX) /* paranoid_hash */,
|
||||
rnd.OneIn(2) /* marked_for_compaction */, id);
|
||||
}
|
||||
result.output_level = rnd.Uniform(10);
|
||||
result.output_path = rnd.RandomString(rnd.Uniform(kStrMaxLen));
|
||||
result.num_output_records = rnd64.Uniform(UINT64_MAX);
|
||||
result.total_bytes = rnd64.Uniform(UINT64_MAX);
|
||||
result.stats.num_output_records = rnd64.Uniform(UINT64_MAX);
|
||||
result.bytes_read = 123;
|
||||
result.bytes_written = rnd64.Uniform(UINT64_MAX);
|
||||
result.stats.elapsed_micros = rnd64.Uniform(UINT64_MAX);
|
||||
@@ -1708,6 +1710,10 @@ TEST_F(CompactionJobTest, ResultSerialization) {
|
||||
ASSERT_FALSE(deserialized_tmp.TEST_Equals(&result, &mismatch));
|
||||
ASSERT_EQ(mismatch, "output_files.unique_id");
|
||||
deserialized_tmp.status.PermitUncheckedError();
|
||||
|
||||
ASSERT_EQ(deserialized_tmp.output_files[0].file_checksum, file_checksum);
|
||||
ASSERT_EQ(deserialized_tmp.output_files[0].file_checksum_func_name,
|
||||
file_checksum_func_name);
|
||||
}
|
||||
|
||||
// Test unknown field
|
||||
|
||||
@@ -62,8 +62,9 @@ class CompactionOutputs {
|
||||
}
|
||||
|
||||
// TODO: Remove it when remote compaction support tiered compaction
|
||||
void SetTotalBytes(uint64_t bytes) { stats_.bytes_written += bytes; }
|
||||
void AddBytesWritten(uint64_t bytes) { stats_.bytes_written += bytes; }
|
||||
void SetNumOutputRecords(uint64_t num) { stats_.num_output_records = num; }
|
||||
void SetNumOutputFiles(uint64_t num) { stats_.num_output_files = num; }
|
||||
|
||||
// TODO: Move the BlobDB builder into CompactionOutputs
|
||||
const std::vector<BlobFileAddition>& GetBlobFileAdditions() const {
|
||||
|
||||
@@ -133,7 +133,8 @@ CompactionPicker::CompactionPicker(const ImmutableOptions& ioptions,
|
||||
CompactionPicker::~CompactionPicker() = default;
|
||||
|
||||
// Delete this compaction from the list of running compactions.
|
||||
void CompactionPicker::ReleaseCompactionFiles(Compaction* c, Status status) {
|
||||
void CompactionPicker::ReleaseCompactionFiles(Compaction* c,
|
||||
const Status& status) {
|
||||
UnregisterCompaction(c);
|
||||
if (!status.ok()) {
|
||||
c->ResetNextCompactionIndex();
|
||||
|
||||
@@ -104,7 +104,7 @@ class CompactionPicker {
|
||||
// Free up the files that participated in a compaction
|
||||
//
|
||||
// Requirement: DB mutex held
|
||||
void ReleaseCompactionFiles(Compaction* c, Status status);
|
||||
void ReleaseCompactionFiles(Compaction* c, const Status& status);
|
||||
|
||||
// Returns true if any one of the specified files are being compacted
|
||||
bool AreFilesInCompaction(const std::vector<FileMetaData*>& files);
|
||||
|
||||
@@ -294,7 +294,7 @@ Compaction* FIFOCompactionPicker::PickSizeCompaction(
|
||||
Compaction* FIFOCompactionPicker::PickTemperatureChangeCompaction(
|
||||
const std::string& cf_name, const MutableCFOptions& mutable_cf_options,
|
||||
const MutableDBOptions& mutable_db_options, VersionStorageInfo* vstorage,
|
||||
LogBuffer* log_buffer) {
|
||||
LogBuffer* log_buffer) const {
|
||||
const std::vector<FileTemperatureAge>& ages =
|
||||
mutable_cf_options.compaction_options_fifo
|
||||
.file_temperature_age_thresholds;
|
||||
@@ -344,12 +344,10 @@ Compaction* FIFOCompactionPicker::PickTemperatureChangeCompaction(
|
||||
Temperature compaction_target_temp = Temperature::kLastTemperature;
|
||||
if (current_time > min_age) {
|
||||
uint64_t create_time_threshold = current_time - min_age;
|
||||
uint64_t compaction_size = 0;
|
||||
// We will ideally identify a file qualifying for temperature change by
|
||||
// knowing the timestamp for the youngest entry in the file. However, right
|
||||
// now we don't have the information. We infer it by looking at timestamp of
|
||||
// the previous file's (which is just younger) oldest entry's timestamp.
|
||||
Temperature cur_target_temp;
|
||||
// avoid index underflow
|
||||
assert(level_files.size() >= 1);
|
||||
for (size_t index = level_files.size() - 1; index >= 1; --index) {
|
||||
@@ -374,7 +372,7 @@ Compaction* FIFOCompactionPicker::PickTemperatureChangeCompaction(
|
||||
// cur_file is too fresh
|
||||
break;
|
||||
}
|
||||
cur_target_temp = ages[0].temperature;
|
||||
Temperature cur_target_temp = ages[0].temperature;
|
||||
for (size_t i = 1; i < ages.size(); ++i) {
|
||||
if (current_time >= ages[i].age &&
|
||||
oldest_ancestor_time <= current_time - ages[i].age) {
|
||||
@@ -382,35 +380,20 @@ Compaction* FIFOCompactionPicker::PickTemperatureChangeCompaction(
|
||||
}
|
||||
}
|
||||
if (cur_file->temperature == cur_target_temp) {
|
||||
if (inputs[0].empty()) {
|
||||
continue;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// cur_file needs to change temperature
|
||||
if (compaction_target_temp == Temperature::kLastTemperature) {
|
||||
assert(inputs[0].empty());
|
||||
compaction_target_temp = cur_target_temp;
|
||||
} else if (cur_target_temp != compaction_target_temp) {
|
||||
assert(!inputs[0].empty());
|
||||
break;
|
||||
}
|
||||
if (inputs[0].empty() || compaction_size + cur_file->fd.GetFileSize() <=
|
||||
mutable_cf_options.max_compaction_bytes) {
|
||||
inputs[0].files.push_back(cur_file);
|
||||
compaction_size += cur_file->fd.GetFileSize();
|
||||
ROCKS_LOG_BUFFER(
|
||||
log_buffer,
|
||||
"[%s] FIFO compaction: picking file %" PRIu64
|
||||
" with next file's oldest time %" PRIu64 " for temperature %s.",
|
||||
cf_name.c_str(), cur_file->fd.GetNumber(), oldest_ancestor_time,
|
||||
temperature_to_string[cur_target_temp].c_str());
|
||||
}
|
||||
if (compaction_size > mutable_cf_options.max_compaction_bytes) {
|
||||
break;
|
||||
}
|
||||
assert(compaction_target_temp == Temperature::kLastTemperature);
|
||||
compaction_target_temp = cur_target_temp;
|
||||
inputs[0].files.push_back(cur_file);
|
||||
ROCKS_LOG_BUFFER(
|
||||
log_buffer,
|
||||
"[%s] FIFO compaction: picking file %" PRIu64
|
||||
" with next file's oldest time %" PRIu64 " for temperature %s.",
|
||||
cf_name.c_str(), cur_file->fd.GetNumber(), oldest_ancestor_time,
|
||||
temperature_to_string[cur_target_temp].c_str());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -418,7 +401,9 @@ Compaction* FIFOCompactionPicker::PickTemperatureChangeCompaction(
|
||||
return nullptr;
|
||||
}
|
||||
assert(compaction_target_temp != Temperature::kLastTemperature);
|
||||
|
||||
// Only compact one file at a time.
|
||||
assert(inputs.size() == 1);
|
||||
assert(inputs[0].size() == 1);
|
||||
Compaction* c = new Compaction(
|
||||
vstorage, ioptions_, mutable_cf_options, mutable_db_options,
|
||||
std::move(inputs), 0, 0 /* output file size limit */,
|
||||
|
||||
@@ -53,9 +53,10 @@ class FIFOCompactionPicker : public CompactionPicker {
|
||||
VersionStorageInfo* version,
|
||||
LogBuffer* log_buffer);
|
||||
|
||||
// Will pick one file to compact at a time, starting from the oldest file.
|
||||
Compaction* PickTemperatureChangeCompaction(
|
||||
const std::string& cf_name, const MutableCFOptions& mutable_cf_options,
|
||||
const MutableDBOptions& mutable_db_options, VersionStorageInfo* vstorage,
|
||||
LogBuffer* log_buffer);
|
||||
LogBuffer* log_buffer) const;
|
||||
};
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -1119,48 +1119,6 @@ TEST_F(CompactionPickerTest, FIFOToCold1) {
|
||||
ASSERT_EQ(3U, compaction->input(0, 0)->fd.GetNumber());
|
||||
}
|
||||
|
||||
TEST_F(CompactionPickerTest, FIFOToCold2) {
|
||||
NewVersionStorage(1, kCompactionStyleFIFO);
|
||||
const uint64_t kFileSize = 100000;
|
||||
const uint64_t kMaxSize = kFileSize * 100000;
|
||||
uint64_t kColdThreshold = 2000;
|
||||
|
||||
fifo_options_.max_table_files_size = kMaxSize;
|
||||
fifo_options_.file_temperature_age_thresholds = {
|
||||
{Temperature::kCold, kColdThreshold}};
|
||||
mutable_cf_options_.compaction_options_fifo = fifo_options_;
|
||||
mutable_cf_options_.level0_file_num_compaction_trigger = 100;
|
||||
mutable_cf_options_.max_compaction_bytes = kFileSize * 100;
|
||||
FIFOCompactionPicker fifo_compaction_picker(ioptions_, &icmp_);
|
||||
|
||||
int64_t current_time = 0;
|
||||
ASSERT_OK(Env::Default()->GetCurrentTime(¤t_time));
|
||||
uint64_t threshold_time =
|
||||
static_cast<uint64_t>(current_time) - kColdThreshold;
|
||||
Add(0, 6U, "240", "290", 2 * kFileSize, 0, 2900, 3000, 0, true,
|
||||
Temperature::kUnknown, static_cast<uint64_t>(current_time) - 100);
|
||||
Add(0, 4U, "260", "300", 1 * kFileSize, 0, 2500, 2600, 0, true,
|
||||
Temperature::kUnknown, threshold_time);
|
||||
// The following two files qualify for compaction to kCold.
|
||||
Add(0, 3U, "200", "300", 4 * kFileSize, 0, 2300, 2400, 0, true,
|
||||
Temperature::kUnknown, threshold_time - 3000);
|
||||
Add(0, 2U, "200", "300", 4 * kFileSize, 0, 2100, 2200, 0, true,
|
||||
Temperature::kUnknown, threshold_time - 4000);
|
||||
UpdateVersionStorageInfo();
|
||||
|
||||
ASSERT_EQ(fifo_compaction_picker.NeedsCompaction(vstorage_.get()), true);
|
||||
std::unique_ptr<Compaction> compaction(fifo_compaction_picker.PickCompaction(
|
||||
cf_name_, mutable_cf_options_, mutable_db_options_, vstorage_.get(),
|
||||
&log_buffer_));
|
||||
ASSERT_TRUE(compaction.get() != nullptr);
|
||||
ASSERT_EQ(compaction->compaction_reason(),
|
||||
CompactionReason::kChangeTemperature);
|
||||
ASSERT_EQ(compaction->output_temperature(), Temperature::kCold);
|
||||
ASSERT_EQ(2U, compaction->num_input_files(0));
|
||||
ASSERT_EQ(2U, compaction->input(0, 0)->fd.GetNumber());
|
||||
ASSERT_EQ(3U, compaction->input(0, 1)->fd.GetNumber());
|
||||
}
|
||||
|
||||
TEST_F(CompactionPickerTest, FIFOToColdMaxCompactionSize) {
|
||||
NewVersionStorage(1, kCompactionStyleFIFO);
|
||||
const uint64_t kFileSize = 100000;
|
||||
@@ -1202,10 +1160,10 @@ TEST_F(CompactionPickerTest, FIFOToColdMaxCompactionSize) {
|
||||
ASSERT_TRUE(compaction.get() != nullptr);
|
||||
ASSERT_EQ(compaction->compaction_reason(),
|
||||
CompactionReason::kChangeTemperature);
|
||||
// Compaction picker picks older files first and picks one file at a time.
|
||||
ASSERT_EQ(compaction->output_temperature(), Temperature::kCold);
|
||||
ASSERT_EQ(2U, compaction->num_input_files(0));
|
||||
ASSERT_EQ(1U, compaction->num_input_files(0));
|
||||
ASSERT_EQ(1U, compaction->input(0, 0)->fd.GetNumber());
|
||||
ASSERT_EQ(2U, compaction->input(0, 1)->fd.GetNumber());
|
||||
}
|
||||
|
||||
TEST_F(CompactionPickerTest, FIFOToColdWithExistingCold) {
|
||||
@@ -1248,10 +1206,10 @@ TEST_F(CompactionPickerTest, FIFOToColdWithExistingCold) {
|
||||
ASSERT_TRUE(compaction.get() != nullptr);
|
||||
ASSERT_EQ(compaction->compaction_reason(),
|
||||
CompactionReason::kChangeTemperature);
|
||||
// Compaction picker picks older files first and picks one file at a time.
|
||||
ASSERT_EQ(compaction->output_temperature(), Temperature::kCold);
|
||||
ASSERT_EQ(1U, compaction->num_input_files(0));
|
||||
ASSERT_EQ(2U, compaction->input(0, 0)->fd.GetNumber());
|
||||
ASSERT_EQ(2U, compaction->num_input_files(0));
|
||||
ASSERT_EQ(3U, compaction->input(0, 1)->fd.GetNumber());
|
||||
}
|
||||
|
||||
TEST_F(CompactionPickerTest, FIFOToColdWithHotBetweenCold) {
|
||||
@@ -1299,7 +1257,7 @@ TEST_F(CompactionPickerTest, FIFOToColdWithHotBetweenCold) {
|
||||
ASSERT_EQ(2U, compaction->input(0, 0)->fd.GetNumber());
|
||||
}
|
||||
|
||||
TEST_F(CompactionPickerTest, FIFOToColdAndWarm) {
|
||||
TEST_F(CompactionPickerTest, FIFOToHotAndWarm) {
|
||||
NewVersionStorage(1, kCompactionStyleFIFO);
|
||||
const uint64_t kFileSize = 100000;
|
||||
const uint64_t kMaxSize = kFileSize * 100000;
|
||||
@@ -1344,11 +1302,10 @@ TEST_F(CompactionPickerTest, FIFOToColdAndWarm) {
|
||||
ASSERT_TRUE(compaction.get() != nullptr);
|
||||
ASSERT_EQ(compaction->compaction_reason(),
|
||||
CompactionReason::kChangeTemperature);
|
||||
// Assumes compaction picker picks older files first.
|
||||
// Compaction picker picks older files first and picks one file at a time.
|
||||
ASSERT_EQ(compaction->output_temperature(), Temperature::kWarm);
|
||||
ASSERT_EQ(2U, compaction->num_input_files(0));
|
||||
ASSERT_EQ(1U, compaction->num_input_files(0));
|
||||
ASSERT_EQ(1U, compaction->input(0, 0)->fd.GetNumber());
|
||||
ASSERT_EQ(2U, compaction->input(0, 1)->fd.GetNumber());
|
||||
}
|
||||
|
||||
TEST_F(CompactionPickerTest, CompactionPriMinOverlapping1) {
|
||||
|
||||
@@ -39,12 +39,8 @@ CompactionJob::ProcessKeyValueCompactionWithCompactionService(
|
||||
MakeTableFileName(file->fd.GetNumber()));
|
||||
}
|
||||
}
|
||||
compaction_input.column_family.name =
|
||||
compaction->column_family_data()->GetName();
|
||||
compaction_input.column_family.options =
|
||||
compaction->column_family_data()->GetLatestCFOptions();
|
||||
compaction_input.db_options =
|
||||
BuildDBOptions(db_options_, mutable_db_options_copy_);
|
||||
|
||||
compaction_input.cf_name = compaction->column_family_data()->GetName();
|
||||
compaction_input.snapshots = existing_snapshots_;
|
||||
compaction_input.has_begin = sub_compact->start.has_value();
|
||||
compaction_input.begin =
|
||||
@@ -52,6 +48,14 @@ CompactionJob::ProcessKeyValueCompactionWithCompactionService(
|
||||
compaction_input.has_end = sub_compact->end.has_value();
|
||||
compaction_input.end =
|
||||
compaction_input.has_end ? sub_compact->end->ToString() : "";
|
||||
compaction_input.options_file_number =
|
||||
sub_compact->compaction->input_version()
|
||||
->version_set()
|
||||
->options_file_number();
|
||||
|
||||
TEST_SYNC_POINT_CALLBACK(
|
||||
"CompactionServiceJob::ProcessKeyValueCompactionWithCompactionService",
|
||||
&compaction_input);
|
||||
|
||||
std::string compaction_input_binary;
|
||||
Status s = compaction_input.Write(&compaction_input_binary);
|
||||
@@ -70,10 +74,13 @@ CompactionJob::ProcessKeyValueCompactionWithCompactionService(
|
||||
ROCKS_LOG_INFO(
|
||||
db_options_.info_log,
|
||||
"[%s] [JOB %d] Starting remote compaction (output level: %d): %s",
|
||||
compaction_input.column_family.name.c_str(), job_id_,
|
||||
compaction->column_family_data()->GetName().c_str(), job_id_,
|
||||
compaction_input.output_level, input_files_oss.str().c_str());
|
||||
CompactionServiceJobInfo info(dbname_, db_id_, db_session_id_,
|
||||
GetCompactionId(sub_compact), thread_pri_);
|
||||
CompactionServiceJobInfo info(
|
||||
dbname_, db_id_, db_session_id_, GetCompactionId(sub_compact),
|
||||
thread_pri_, compaction->compaction_reason(),
|
||||
compaction->is_full_compaction(), compaction->is_manual_compaction(),
|
||||
compaction->bottommost_level());
|
||||
CompactionServiceScheduleResponse response =
|
||||
db_options_.compaction_service->Schedule(info, compaction_input_binary);
|
||||
switch (response.status) {
|
||||
@@ -84,13 +91,14 @@ CompactionJob::ProcessKeyValueCompactionWithCompactionService(
|
||||
"CompactionService failed to schedule a remote compaction job.");
|
||||
ROCKS_LOG_WARN(db_options_.info_log,
|
||||
"[%s] [JOB %d] Remote compaction failed to start.",
|
||||
compaction_input.column_family.name.c_str(), job_id_);
|
||||
compaction->column_family_data()->GetName().c_str(),
|
||||
job_id_);
|
||||
return response.status;
|
||||
case CompactionServiceJobStatus::kUseLocal:
|
||||
ROCKS_LOG_INFO(
|
||||
db_options_.info_log,
|
||||
"[%s] [JOB %d] Remote compaction fallback to local by API (Schedule)",
|
||||
compaction_input.column_family.name.c_str(), job_id_);
|
||||
compaction->column_family_data()->GetName().c_str(), job_id_);
|
||||
return response.status;
|
||||
default:
|
||||
assert(false); // unknown status
|
||||
@@ -99,7 +107,7 @@ CompactionJob::ProcessKeyValueCompactionWithCompactionService(
|
||||
|
||||
ROCKS_LOG_INFO(db_options_.info_log,
|
||||
"[%s] [JOB %d] Waiting for remote compaction...",
|
||||
compaction_input.column_family.name.c_str(), job_id_);
|
||||
compaction->column_family_data()->GetName().c_str(), job_id_);
|
||||
std::string compaction_result_binary;
|
||||
CompactionServiceJobStatus compaction_status =
|
||||
db_options_.compaction_service->Wait(response.scheduled_job_id,
|
||||
@@ -109,7 +117,7 @@ CompactionJob::ProcessKeyValueCompactionWithCompactionService(
|
||||
ROCKS_LOG_INFO(
|
||||
db_options_.info_log,
|
||||
"[%s] [JOB %d] Remote compaction fallback to local by API (Wait)",
|
||||
compaction_input.column_family.name.c_str(), job_id_);
|
||||
compaction->column_family_data()->GetName().c_str(), job_id_);
|
||||
return compaction_status;
|
||||
}
|
||||
|
||||
@@ -134,9 +142,9 @@ CompactionJob::ProcessKeyValueCompactionWithCompactionService(
|
||||
"result is returned).");
|
||||
compaction_result.status.PermitUncheckedError();
|
||||
}
|
||||
ROCKS_LOG_WARN(db_options_.info_log,
|
||||
"[%s] [JOB %d] Remote compaction failed.",
|
||||
compaction_input.column_family.name.c_str(), job_id_);
|
||||
ROCKS_LOG_WARN(
|
||||
db_options_.info_log, "[%s] [JOB %d] Remote compaction failed.",
|
||||
compaction->column_family_data()->GetName().c_str(), job_id_);
|
||||
return compaction_status;
|
||||
}
|
||||
|
||||
@@ -162,7 +170,7 @@ CompactionJob::ProcessKeyValueCompactionWithCompactionService(
|
||||
db_options_.info_log,
|
||||
"[%s] [JOB %d] Received remote compaction result, output path: "
|
||||
"%s, files: %s",
|
||||
compaction_input.column_family.name.c_str(), job_id_,
|
||||
compaction->column_family_data()->GetName().c_str(), job_id_,
|
||||
compaction_result.output_path.c_str(), output_files_oss.str().c_str());
|
||||
|
||||
// Installation Starts
|
||||
@@ -195,6 +203,8 @@ CompactionJob::ProcessKeyValueCompactionWithCompactionService(
|
||||
meta.oldest_ancester_time = file.oldest_ancester_time;
|
||||
meta.file_creation_time = file.file_creation_time;
|
||||
meta.epoch_number = file.epoch_number;
|
||||
meta.file_checksum = file.file_checksum;
|
||||
meta.file_checksum_func_name = file.file_checksum_func_name;
|
||||
meta.marked_for_compaction = file.marked_for_compaction;
|
||||
meta.unique_id = file.unique_id;
|
||||
|
||||
@@ -205,8 +215,10 @@ CompactionJob::ProcessKeyValueCompactionWithCompactionService(
|
||||
}
|
||||
sub_compact->compaction_job_stats = compaction_result.stats;
|
||||
sub_compact->Current().SetNumOutputRecords(
|
||||
compaction_result.num_output_records);
|
||||
sub_compact->Current().SetTotalBytes(compaction_result.total_bytes);
|
||||
compaction_result.stats.num_output_records);
|
||||
sub_compact->Current().SetNumOutputFiles(
|
||||
compaction_result.stats.num_output_files);
|
||||
sub_compact->Current().AddBytesWritten(compaction_result.bytes_written);
|
||||
RecordTick(stats_, REMOTE_COMPACT_READ_BYTES, compaction_result.bytes_read);
|
||||
RecordTick(stats_, REMOTE_COMPACT_WRITE_BYTES,
|
||||
compaction_result.bytes_written);
|
||||
@@ -226,6 +238,18 @@ void CompactionServiceCompactionJob::RecordCompactionIOStats() {
|
||||
CompactionJob::RecordCompactionIOStats();
|
||||
}
|
||||
|
||||
void CompactionServiceCompactionJob::UpdateCompactionJobStats(
|
||||
const InternalStats::CompactionStats& stats) const {
|
||||
compaction_job_stats_->elapsed_micros = stats.micros;
|
||||
|
||||
// output information only in remote compaction
|
||||
compaction_job_stats_->total_output_bytes = stats.bytes_written;
|
||||
compaction_job_stats_->total_output_bytes_blob = stats.bytes_written_blob;
|
||||
compaction_job_stats_->num_output_records = stats.num_output_records;
|
||||
compaction_job_stats_->num_output_files = stats.num_output_files;
|
||||
compaction_job_stats_->num_output_files_blob = stats.num_output_files_blob;
|
||||
}
|
||||
|
||||
CompactionServiceCompactionJob::CompactionServiceCompactionJob(
|
||||
int job_id, Compaction* compaction, const ImmutableDBOptions& db_options,
|
||||
const MutableDBOptions& mutable_db_options, const FileOptions& file_options,
|
||||
@@ -261,11 +285,11 @@ Status CompactionServiceCompactionJob::Run() {
|
||||
|
||||
auto* c = compact_->compaction;
|
||||
assert(c->column_family_data() != nullptr);
|
||||
assert(c->column_family_data()->current()->storage_info()->NumLevelFiles(
|
||||
compact_->compaction->level()) > 0);
|
||||
const VersionStorageInfo* storage_info = c->input_version()->storage_info();
|
||||
assert(storage_info);
|
||||
assert(storage_info->NumLevelFiles(compact_->compaction->level()) > 0);
|
||||
write_hint_ = storage_info->CalculateSSTWriteHint(c->output_level());
|
||||
|
||||
write_hint_ =
|
||||
c->column_family_data()->CalculateSSTWriteHint(c->output_level());
|
||||
bottommost_level_ = c->bottommost_level();
|
||||
|
||||
Slice begin = compaction_input_.begin;
|
||||
@@ -280,6 +304,9 @@ Status CompactionServiceCompactionJob::Run() {
|
||||
|
||||
log_buffer_->FlushBufferToLog();
|
||||
LogCompaction();
|
||||
|
||||
compaction_result_->stats.Reset();
|
||||
|
||||
const uint64_t start_micros = db_options_.clock->NowMicros();
|
||||
c->GetOrInitInputTableProperties();
|
||||
|
||||
@@ -320,20 +347,32 @@ Status CompactionServiceCompactionJob::Run() {
|
||||
if (status.ok()) {
|
||||
status = io_s;
|
||||
}
|
||||
if (status.ok()) {
|
||||
// TODO: Add verify_table()
|
||||
}
|
||||
|
||||
// Finish up all book-keeping to unify the subcompaction results
|
||||
compact_->AggregateCompactionStats(compaction_stats_, *compaction_job_stats_);
|
||||
UpdateCompactionStats();
|
||||
RecordCompactionIOStats();
|
||||
|
||||
LogFlush(db_options_.info_log);
|
||||
compact_->status = status;
|
||||
compact_->status.PermitUncheckedError();
|
||||
|
||||
// Build compaction result
|
||||
// Build Compaction Job Stats
|
||||
|
||||
// 1. Aggregate CompactionOutputStats into Internal Compaction Stats
|
||||
// (compaction_stats_) and aggregate Compaction Job Stats
|
||||
// (compaction_job_stats_) from the sub compactions
|
||||
compact_->AggregateCompactionStats(compaction_stats_, *compaction_job_stats_);
|
||||
|
||||
// 2. Update the Output information in the Compaction Job Stats with
|
||||
// aggregated Internal Compaction Stats.
|
||||
UpdateCompactionJobStats(compaction_stats_.stats);
|
||||
|
||||
// 3. Set fields that are not propagated as part of aggregations above
|
||||
compaction_result_->stats.is_manual_compaction = c->is_manual_compaction();
|
||||
compaction_result_->stats.is_full_compaction = c->is_full_compaction();
|
||||
compaction_result_->stats.is_remote_compaction = true;
|
||||
|
||||
// 4. Update IO Stats that are not part of the aggregations above (bytes_read,
|
||||
// bytes_written)
|
||||
RecordCompactionIOStats();
|
||||
|
||||
// Build Output
|
||||
compaction_result_->output_level = compact_->compaction->output_level();
|
||||
compaction_result_->output_path = output_path_;
|
||||
for (const auto& output_file : sub_compact->GetOutputs()) {
|
||||
@@ -342,16 +381,13 @@ Status CompactionServiceCompactionJob::Run() {
|
||||
MakeTableFileName(meta.fd.GetNumber()), 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,
|
||||
output_file.validator.GetHash(), meta.marked_for_compaction,
|
||||
meta.unique_id);
|
||||
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);
|
||||
}
|
||||
InternalStats::CompactionStatsFull compaction_stats;
|
||||
sub_compact->AggregateCompactionStats(compaction_stats);
|
||||
compaction_result_->num_output_records =
|
||||
compaction_stats.stats.num_output_records;
|
||||
compaction_result_->total_bytes = compaction_stats.TotalBytesWritten();
|
||||
|
||||
TEST_SYNC_POINT_CALLBACK("CompactionServiceCompactionJob::Run:0",
|
||||
&compaction_result_);
|
||||
return status;
|
||||
}
|
||||
|
||||
@@ -404,42 +440,9 @@ static std::unordered_map<std::string, OptionTypeInfo> cfd_type_info = {
|
||||
};
|
||||
|
||||
static std::unordered_map<std::string, OptionTypeInfo> cs_input_type_info = {
|
||||
{"column_family",
|
||||
OptionTypeInfo::Struct(
|
||||
"column_family", &cfd_type_info,
|
||||
offsetof(struct CompactionServiceInput, column_family),
|
||||
OptionVerificationType::kNormal, OptionTypeFlags::kNone)},
|
||||
{"db_options",
|
||||
{offsetof(struct CompactionServiceInput, db_options),
|
||||
OptionType::kConfigurable, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone,
|
||||
[](const ConfigOptions& opts, const std::string& /*name*/,
|
||||
const std::string& value, void* addr) {
|
||||
auto options = static_cast<DBOptions*>(addr);
|
||||
return GetDBOptionsFromString(opts, DBOptions(), value, options);
|
||||
},
|
||||
[](const ConfigOptions& opts, const std::string& /*name*/,
|
||||
const void* addr, std::string* value) {
|
||||
const auto options = static_cast<const DBOptions*>(addr);
|
||||
std::string result;
|
||||
auto status = GetStringFromDBOptions(opts, *options, &result);
|
||||
*value = "{" + result + "}";
|
||||
return status;
|
||||
},
|
||||
[](const ConfigOptions& opts, const std::string& name, const void* addr1,
|
||||
const void* addr2, std::string* mismatch) {
|
||||
const auto this_one = static_cast<const DBOptions*>(addr1);
|
||||
const auto that_one = static_cast<const DBOptions*>(addr2);
|
||||
auto this_conf = DBOptionsAsConfigurable(*this_one);
|
||||
auto that_conf = DBOptionsAsConfigurable(*that_one);
|
||||
std::string mismatch_opt;
|
||||
bool result =
|
||||
this_conf->AreEquivalent(opts, that_conf.get(), &mismatch_opt);
|
||||
if (!result) {
|
||||
*mismatch = name + "." + mismatch_opt;
|
||||
}
|
||||
return result;
|
||||
}}},
|
||||
{"cf_name",
|
||||
{offsetof(struct CompactionServiceInput, cf_name),
|
||||
OptionType::kEncodedString}},
|
||||
{"snapshots", OptionTypeInfo::Vector<uint64_t>(
|
||||
offsetof(struct CompactionServiceInput, snapshots),
|
||||
OptionVerificationType::kNormal, OptionTypeFlags::kNone,
|
||||
@@ -467,6 +470,10 @@ static std::unordered_map<std::string, OptionTypeInfo> cs_input_type_info = {
|
||||
{"end",
|
||||
{offsetof(struct CompactionServiceInput, end), OptionType::kEncodedString,
|
||||
OptionVerificationType::kNormal, OptionTypeFlags::kNone}},
|
||||
{"options_file_number",
|
||||
{offsetof(struct CompactionServiceInput, options_file_number),
|
||||
OptionType::kUInt64T, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone}},
|
||||
};
|
||||
|
||||
static std::unordered_map<std::string, OptionTypeInfo>
|
||||
@@ -503,6 +510,14 @@ static std::unordered_map<std::string, OptionTypeInfo>
|
||||
{offsetof(struct CompactionServiceOutputFile, epoch_number),
|
||||
OptionType::kUInt64T, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone}},
|
||||
{"file_checksum",
|
||||
{offsetof(struct CompactionServiceOutputFile, file_checksum),
|
||||
OptionType::kEncodedString, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone}},
|
||||
{"file_checksum_func_name",
|
||||
{offsetof(struct CompactionServiceOutputFile, file_checksum_func_name),
|
||||
OptionType::kEncodedString, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone}},
|
||||
{"paranoid_hash",
|
||||
{offsetof(struct CompactionServiceOutputFile, paranoid_hash),
|
||||
OptionType::kUInt64T, OptionVerificationType::kNormal,
|
||||
@@ -563,6 +578,10 @@ static std::unordered_map<std::string, OptionTypeInfo>
|
||||
{offsetof(struct CompactionJobStats, is_manual_compaction),
|
||||
OptionType::kBoolean, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone}},
|
||||
{"is_remote_compaction",
|
||||
{offsetof(struct CompactionJobStats, is_remote_compaction),
|
||||
OptionType::kBoolean, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone}},
|
||||
{"total_input_bytes",
|
||||
{offsetof(struct CompactionJobStats, total_input_bytes),
|
||||
OptionType::kUInt64T, OptionVerificationType::kNormal,
|
||||
@@ -731,14 +750,6 @@ static std::unordered_map<std::string, OptionTypeInfo> cs_result_type_info = {
|
||||
{offsetof(struct CompactionServiceResult, output_path),
|
||||
OptionType::kEncodedString, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone}},
|
||||
{"num_output_records",
|
||||
{offsetof(struct CompactionServiceResult, num_output_records),
|
||||
OptionType::kUInt64T, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone}},
|
||||
{"total_bytes",
|
||||
{offsetof(struct CompactionServiceResult, total_bytes),
|
||||
OptionType::kUInt64T, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone}},
|
||||
{"bytes_read",
|
||||
{offsetof(struct CompactionServiceResult, bytes_read),
|
||||
OptionType::kUInt64T, OptionVerificationType::kNormal,
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
|
||||
#include "db/db_test_util.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "rocksdb/utilities/options_util.h"
|
||||
#include "table/unique_id_impl.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
@@ -21,8 +21,10 @@ class MyTestCompactionService : public CompactionService {
|
||||
: db_path_(std::move(db_path)),
|
||||
options_(options),
|
||||
statistics_(statistics),
|
||||
start_info_("na", "na", "na", 0, Env::TOTAL),
|
||||
wait_info_("na", "na", "na", 0, Env::TOTAL),
|
||||
start_info_("na", "na", "na", 0, Env::TOTAL, CompactionReason::kUnknown,
|
||||
false, false, false),
|
||||
wait_info_("na", "na", "na", 0, Env::TOTAL, CompactionReason::kUnknown,
|
||||
false, false, false),
|
||||
listeners_(listeners),
|
||||
table_properties_collector_factories_(
|
||||
std::move(table_properties_collector_factories)) {}
|
||||
@@ -97,8 +99,12 @@ class MyTestCompactionService : public CompactionService {
|
||||
Status s =
|
||||
DB::OpenAndCompact(options, db_path_, db_path_ + "/" + scheduled_job_id,
|
||||
compaction_input, result, options_override);
|
||||
if (is_override_wait_result_) {
|
||||
*result = override_wait_result_;
|
||||
{
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
if (is_override_wait_result_) {
|
||||
*result = override_wait_result_;
|
||||
}
|
||||
result_ = *result;
|
||||
}
|
||||
compaction_num_.fetch_add(1);
|
||||
if (s.ok()) {
|
||||
@@ -141,6 +147,10 @@ class MyTestCompactionService : public CompactionService {
|
||||
|
||||
void SetCanceled(bool canceled) { canceled_ = canceled; }
|
||||
|
||||
void GetResult(CompactionServiceResult* deserialized) {
|
||||
CompactionServiceResult::Read(result_, deserialized).PermitUncheckedError();
|
||||
}
|
||||
|
||||
CompactionServiceJobStatus GetFinalCompactionServiceJobStatus() {
|
||||
return final_updated_status_.load();
|
||||
}
|
||||
@@ -162,6 +172,7 @@ class MyTestCompactionService : public CompactionService {
|
||||
CompactionServiceJobStatus override_wait_status_ =
|
||||
CompactionServiceJobStatus::kFailure;
|
||||
bool is_override_wait_result_ = false;
|
||||
std::string result_;
|
||||
std::string override_wait_result_;
|
||||
std::vector<std::shared_ptr<EventListener>> listeners_;
|
||||
std::vector<std::shared_ptr<TablePropertiesCollectorFactory>>
|
||||
@@ -331,6 +342,34 @@ TEST_F(CompactionServiceTest, BasicCompactions) {
|
||||
ReopenWithColumnFamilies({kDefaultColumnFamilyName, "cf_1", "cf_2", "cf_3"},
|
||||
options);
|
||||
ASSERT_GT(verify_passed, 0);
|
||||
CompactionServiceResult result;
|
||||
my_cs->GetResult(&result);
|
||||
if (s.IsAborted()) {
|
||||
ASSERT_NOK(result.status);
|
||||
} else {
|
||||
ASSERT_OK(result.status);
|
||||
}
|
||||
ASSERT_GE(result.stats.elapsed_micros, 1);
|
||||
ASSERT_GE(result.stats.cpu_micros, 1);
|
||||
|
||||
ASSERT_EQ(20, result.stats.num_output_records);
|
||||
ASSERT_EQ(result.output_files.size(), result.stats.num_output_files);
|
||||
|
||||
uint64_t total_size = 0;
|
||||
for (auto output_file : result.output_files) {
|
||||
std::string file_name = result.output_path + "/" + output_file.file_name;
|
||||
|
||||
uint64_t file_size = 0;
|
||||
ASSERT_OK(options.env->GetFileSize(file_name, &file_size));
|
||||
ASSERT_GT(file_size, 0);
|
||||
total_size += file_size;
|
||||
}
|
||||
ASSERT_EQ(total_size, result.stats.total_output_bytes);
|
||||
|
||||
ASSERT_TRUE(result.stats.is_remote_compaction);
|
||||
ASSERT_TRUE(result.stats.is_manual_compaction);
|
||||
ASSERT_FALSE(result.stats.is_full_compaction);
|
||||
|
||||
Close();
|
||||
}
|
||||
|
||||
@@ -369,6 +408,507 @@ TEST_F(CompactionServiceTest, ManualCompaction) {
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
ASSERT_GE(my_cs->GetCompactionNum(), comp_num + 1);
|
||||
VerifyTestData();
|
||||
|
||||
CompactionServiceResult result;
|
||||
my_cs->GetResult(&result);
|
||||
ASSERT_OK(result.status);
|
||||
ASSERT_TRUE(result.stats.is_manual_compaction);
|
||||
ASSERT_TRUE(result.stats.is_remote_compaction);
|
||||
}
|
||||
|
||||
TEST_F(CompactionServiceTest, PreservedOptionsLocalCompaction) {
|
||||
Options options = CurrentOptions();
|
||||
options.level0_file_num_compaction_trigger = 2;
|
||||
options.disable_auto_compactions = true;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
Random rnd(301);
|
||||
for (auto i = 0; i < 2; ++i) {
|
||||
for (auto j = 0; j < 10; ++j) {
|
||||
ASSERT_OK(
|
||||
Put("foo" + std::to_string(i * 10 + j), rnd.RandomString(1024)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"CompactionJob::ProcessKeyValueCompaction()::Processing", [&](void* arg) {
|
||||
auto compaction = static_cast<Compaction*>(arg);
|
||||
std::string options_file_name = OptionsFileName(
|
||||
dbname_,
|
||||
compaction->input_version()->version_set()->options_file_number());
|
||||
|
||||
// Change option twice to make sure the very first OPTIONS file gets
|
||||
// purged
|
||||
ASSERT_OK(dbfull()->SetOptions(
|
||||
{{"level0_file_num_compaction_trigger", "4"}}));
|
||||
ASSERT_EQ(4, dbfull()->GetOptions().level0_file_num_compaction_trigger);
|
||||
ASSERT_OK(dbfull()->SetOptions(
|
||||
{{"level0_file_num_compaction_trigger", "6"}}));
|
||||
ASSERT_EQ(6, dbfull()->GetOptions().level0_file_num_compaction_trigger);
|
||||
dbfull()->TEST_DeleteObsoleteFiles();
|
||||
|
||||
// For non-remote compactions, OPTIONS file can be deleted while
|
||||
// using option at the start of the compaction
|
||||
Status s = env_->FileExists(options_file_name);
|
||||
ASSERT_NOK(s);
|
||||
ASSERT_TRUE(s.IsNotFound());
|
||||
// Should be old value
|
||||
ASSERT_EQ(2, compaction->mutable_cf_options()
|
||||
->level0_file_num_compaction_trigger);
|
||||
ASSERT_TRUE(dbfull()->min_options_file_numbers_.empty());
|
||||
});
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
Status s = dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
||||
ASSERT_TRUE(s.ok());
|
||||
}
|
||||
|
||||
TEST_F(CompactionServiceTest, PreservedOptionsRemoteCompaction) {
|
||||
// For non-remote compaction do not preserve options file
|
||||
Options options = CurrentOptions();
|
||||
options.level0_file_num_compaction_trigger = 2;
|
||||
options.disable_auto_compactions = true;
|
||||
ReopenWithCompactionService(&options);
|
||||
GenerateTestData();
|
||||
|
||||
auto my_cs = GetCompactionService();
|
||||
|
||||
Random rnd(301);
|
||||
for (auto i = 0; i < 2; ++i) {
|
||||
for (auto j = 0; j < 10; ++j) {
|
||||
ASSERT_OK(
|
||||
Put("foo" + std::to_string(i * 10 + j), rnd.RandomString(1024)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
|
||||
bool is_primary_called = false;
|
||||
// This will be called twice. One from primary and one from remote.
|
||||
// Try changing the option when called from remote. Otherwise, the new option
|
||||
// will be used
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::BackgroundCompaction:NonTrivial:BeforeRun", [&](void* /*arg*/) {
|
||||
if (!is_primary_called) {
|
||||
is_primary_called = true;
|
||||
return;
|
||||
}
|
||||
// Change the option right before the compaction run
|
||||
ASSERT_OK(dbfull()->SetOptions(
|
||||
{{"level0_file_num_compaction_trigger", "4"}}));
|
||||
ASSERT_EQ(4, dbfull()->GetOptions().level0_file_num_compaction_trigger);
|
||||
dbfull()->TEST_DeleteObsoleteFiles();
|
||||
});
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"CompactionServiceJob::ProcessKeyValueCompactionWithCompactionService",
|
||||
[&](void* arg) {
|
||||
auto input = static_cast<CompactionServiceInput*>(arg);
|
||||
std::string options_file_name =
|
||||
OptionsFileName(dbname_, input->options_file_number);
|
||||
|
||||
ASSERT_OK(env_->FileExists(options_file_name));
|
||||
ASSERT_FALSE(dbfull()->min_options_file_numbers_.empty());
|
||||
ASSERT_EQ(dbfull()->min_options_file_numbers_.front(),
|
||||
input->options_file_number);
|
||||
|
||||
DBOptions db_options;
|
||||
ConfigOptions config_options;
|
||||
std::vector<ColumnFamilyDescriptor> all_column_families;
|
||||
config_options.env = env_;
|
||||
ASSERT_OK(LoadOptionsFromFile(config_options, options_file_name,
|
||||
&db_options, &all_column_families));
|
||||
bool has_cf = false;
|
||||
for (auto& cf : all_column_families) {
|
||||
if (cf.name == input->cf_name) {
|
||||
// Should be old value
|
||||
ASSERT_EQ(2, cf.options.level0_file_num_compaction_trigger);
|
||||
has_cf = true;
|
||||
}
|
||||
}
|
||||
ASSERT_TRUE(has_cf);
|
||||
});
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"CompactionJob::ProcessKeyValueCompaction()::Processing", [&](void* arg) {
|
||||
auto compaction = static_cast<Compaction*>(arg);
|
||||
ASSERT_EQ(2, compaction->mutable_cf_options()
|
||||
->level0_file_num_compaction_trigger);
|
||||
});
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
Status s = dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
||||
ASSERT_TRUE(s.ok());
|
||||
|
||||
CompactionServiceResult result;
|
||||
my_cs->GetResult(&result);
|
||||
ASSERT_OK(result.status);
|
||||
ASSERT_TRUE(result.stats.is_manual_compaction);
|
||||
ASSERT_TRUE(result.stats.is_remote_compaction);
|
||||
}
|
||||
|
||||
class EventVerifier : public EventListener {
|
||||
public:
|
||||
explicit EventVerifier(uint64_t expected_num_input_records,
|
||||
size_t expected_num_input_files,
|
||||
uint64_t expected_num_output_records,
|
||||
size_t expected_num_output_files,
|
||||
const std::string& expected_smallest_output_key_prefix,
|
||||
const std::string& expected_largest_output_key_prefix,
|
||||
bool expected_is_remote_compaction_on_begin,
|
||||
bool expected_is_remote_compaction_on_complete)
|
||||
: expected_num_input_records_(expected_num_input_records),
|
||||
expected_num_input_files_(expected_num_input_files),
|
||||
expected_num_output_records_(expected_num_output_records),
|
||||
expected_num_output_files_(expected_num_output_files),
|
||||
expected_smallest_output_key_prefix_(
|
||||
expected_smallest_output_key_prefix),
|
||||
expected_largest_output_key_prefix_(expected_largest_output_key_prefix),
|
||||
expected_is_remote_compaction_on_begin_(
|
||||
expected_is_remote_compaction_on_begin),
|
||||
expected_is_remote_compaction_on_complete_(
|
||||
expected_is_remote_compaction_on_complete) {}
|
||||
void OnCompactionBegin(DB* /*db*/, const CompactionJobInfo& ci) override {
|
||||
ASSERT_EQ(expected_num_input_files_, ci.input_files.size());
|
||||
ASSERT_EQ(expected_num_input_files_, ci.input_file_infos.size());
|
||||
ASSERT_EQ(expected_is_remote_compaction_on_begin_,
|
||||
ci.stats.is_remote_compaction);
|
||||
ASSERT_TRUE(ci.stats.is_manual_compaction);
|
||||
ASSERT_FALSE(ci.stats.is_full_compaction);
|
||||
}
|
||||
void OnCompactionCompleted(DB* /*db*/, const CompactionJobInfo& ci) override {
|
||||
ASSERT_GT(ci.stats.elapsed_micros, 0);
|
||||
ASSERT_GT(ci.stats.cpu_micros, 0);
|
||||
ASSERT_EQ(expected_num_input_records_, ci.stats.num_input_records);
|
||||
ASSERT_EQ(expected_num_input_files_, ci.stats.num_input_files);
|
||||
ASSERT_EQ(expected_num_output_records_, ci.stats.num_output_records);
|
||||
ASSERT_EQ(expected_num_output_files_, ci.stats.num_output_files);
|
||||
ASSERT_EQ(expected_smallest_output_key_prefix_,
|
||||
ci.stats.smallest_output_key_prefix);
|
||||
ASSERT_EQ(expected_largest_output_key_prefix_,
|
||||
ci.stats.largest_output_key_prefix);
|
||||
ASSERT_GT(ci.stats.total_input_bytes, 0);
|
||||
ASSERT_GT(ci.stats.total_output_bytes, 0);
|
||||
ASSERT_EQ(ci.stats.num_input_records,
|
||||
ci.stats.num_output_records + ci.stats.num_records_replaced);
|
||||
ASSERT_EQ(expected_is_remote_compaction_on_complete_,
|
||||
ci.stats.is_remote_compaction);
|
||||
ASSERT_TRUE(ci.stats.is_manual_compaction);
|
||||
ASSERT_FALSE(ci.stats.is_full_compaction);
|
||||
}
|
||||
|
||||
private:
|
||||
uint64_t expected_num_input_records_;
|
||||
size_t expected_num_input_files_;
|
||||
uint64_t expected_num_output_records_;
|
||||
size_t expected_num_output_files_;
|
||||
std::string expected_smallest_output_key_prefix_;
|
||||
std::string expected_largest_output_key_prefix_;
|
||||
bool expected_is_remote_compaction_on_begin_;
|
||||
bool expected_is_remote_compaction_on_complete_;
|
||||
};
|
||||
|
||||
TEST_F(CompactionServiceTest, VerifyStats) {
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
auto event_verifier = std::make_shared<EventVerifier>(
|
||||
30 /* expected_num_input_records */, 3 /* expected_num_input_files */,
|
||||
20 /* expected_num_output_records */, 1 /* expected_num_output_files */,
|
||||
"key00000" /* expected_smallest_output_key_prefix */,
|
||||
"key00001" /* expected_largest_output_key_prefix */,
|
||||
true /* expected_is_remote_compaction_on_begin */,
|
||||
true /* expected_is_remote_compaction_on_complete */);
|
||||
options.listeners.push_back(event_verifier);
|
||||
ReopenWithCompactionService(&options);
|
||||
GenerateTestData();
|
||||
|
||||
auto my_cs = GetCompactionService();
|
||||
|
||||
std::string start_str = Key(0);
|
||||
std::string end_str = Key(1);
|
||||
Slice start(start_str);
|
||||
Slice end(end_str);
|
||||
uint64_t comp_num = my_cs->GetCompactionNum();
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), &start, &end));
|
||||
ASSERT_GE(my_cs->GetCompactionNum(), comp_num + 1);
|
||||
VerifyTestData();
|
||||
|
||||
CompactionServiceResult result;
|
||||
my_cs->GetResult(&result);
|
||||
ASSERT_OK(result.status);
|
||||
ASSERT_TRUE(result.stats.is_manual_compaction);
|
||||
ASSERT_TRUE(result.stats.is_remote_compaction);
|
||||
}
|
||||
|
||||
TEST_F(CompactionServiceTest, VerifyStatsLocalFallback) {
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
auto event_verifier = std::make_shared<EventVerifier>(
|
||||
30 /* expected_num_input_records */, 3 /* expected_num_input_files */,
|
||||
20 /* expected_num_output_records */, 1 /* expected_num_output_files */,
|
||||
"key00000" /* expected_smallest_output_key_prefix */,
|
||||
"key00001" /* expected_largest_output_key_prefix */,
|
||||
true /* expected_is_remote_compaction_on_begin */,
|
||||
false /* expected_is_remote_compaction_on_complete */);
|
||||
options.listeners.push_back(event_verifier);
|
||||
ReopenWithCompactionService(&options);
|
||||
GenerateTestData();
|
||||
|
||||
auto my_cs = GetCompactionService();
|
||||
my_cs->OverrideStartStatus(CompactionServiceJobStatus::kUseLocal);
|
||||
|
||||
std::string start_str = Key(0);
|
||||
std::string end_str = Key(1);
|
||||
Slice start(start_str);
|
||||
Slice end(end_str);
|
||||
uint64_t comp_num = my_cs->GetCompactionNum();
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), &start, &end));
|
||||
// Remote Compaction did not happen
|
||||
ASSERT_EQ(my_cs->GetCompactionNum(), comp_num);
|
||||
VerifyTestData();
|
||||
}
|
||||
|
||||
TEST_F(CompactionServiceTest, CorruptedOutput) {
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
ReopenWithCompactionService(&options);
|
||||
GenerateTestData();
|
||||
|
||||
auto my_cs = GetCompactionService();
|
||||
|
||||
std::string start_str = Key(15);
|
||||
std::string end_str = Key(45);
|
||||
Slice start(start_str);
|
||||
Slice end(end_str);
|
||||
uint64_t comp_num = my_cs->GetCompactionNum();
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"CompactionServiceCompactionJob::Run:0", [&](void* arg) {
|
||||
CompactionServiceResult* compaction_result =
|
||||
*(static_cast<CompactionServiceResult**>(arg));
|
||||
ASSERT_TRUE(compaction_result != nullptr &&
|
||||
!compaction_result->output_files.empty());
|
||||
// Corrupt files here
|
||||
for (const auto& output_file : compaction_result->output_files) {
|
||||
std::string file_name =
|
||||
compaction_result->output_path + "/" + output_file.file_name;
|
||||
|
||||
uint64_t file_size = 0;
|
||||
Status s = options.env->GetFileSize(file_name, &file_size);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_GT(file_size, 0);
|
||||
|
||||
ASSERT_OK(test::CorruptFile(env_, file_name, 0,
|
||||
static_cast<int>(file_size),
|
||||
true /* verifyChecksum */));
|
||||
}
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
// CompactRange() should fail
|
||||
Status s = db_->CompactRange(CompactRangeOptions(), &start, &end);
|
||||
ASSERT_NOK(s);
|
||||
ASSERT_TRUE(s.IsCorruption());
|
||||
|
||||
ASSERT_GE(my_cs->GetCompactionNum(), comp_num + 1);
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
|
||||
// On the worker side, the compaction is considered success
|
||||
// Verification is done on the primary side
|
||||
CompactionServiceResult result;
|
||||
my_cs->GetResult(&result);
|
||||
ASSERT_OK(result.status);
|
||||
ASSERT_TRUE(result.stats.is_manual_compaction);
|
||||
ASSERT_TRUE(result.stats.is_remote_compaction);
|
||||
}
|
||||
|
||||
TEST_F(CompactionServiceTest, CorruptedOutputParanoidFileCheck) {
|
||||
for (bool paranoid_file_check_enabled : {false, true}) {
|
||||
SCOPED_TRACE("paranoid_file_check_enabled=" +
|
||||
std::to_string(paranoid_file_check_enabled));
|
||||
|
||||
Options options = CurrentOptions();
|
||||
Destroy(options);
|
||||
options.disable_auto_compactions = true;
|
||||
options.paranoid_file_checks = paranoid_file_check_enabled;
|
||||
ReopenWithCompactionService(&options);
|
||||
GenerateTestData();
|
||||
|
||||
auto my_cs = GetCompactionService();
|
||||
|
||||
std::string start_str = Key(15);
|
||||
std::string end_str = Key(45);
|
||||
Slice start(start_str);
|
||||
Slice end(end_str);
|
||||
uint64_t comp_num = my_cs->GetCompactionNum();
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"CompactionServiceCompactionJob::Run:0", [&](void* arg) {
|
||||
CompactionServiceResult* compaction_result =
|
||||
*(static_cast<CompactionServiceResult**>(arg));
|
||||
ASSERT_TRUE(compaction_result != nullptr &&
|
||||
!compaction_result->output_files.empty());
|
||||
// Corrupt files here
|
||||
for (const auto& output_file : compaction_result->output_files) {
|
||||
std::string file_name =
|
||||
compaction_result->output_path + "/" + output_file.file_name;
|
||||
|
||||
// Corrupt very small range of bytes. This corruption is so small
|
||||
// that this isn't caught by default light-weight check
|
||||
ASSERT_OK(test::CorruptFile(env_, file_name, 0, 1,
|
||||
false /* verifyChecksum */));
|
||||
}
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
Status s = db_->CompactRange(CompactRangeOptions(), &start, &end);
|
||||
if (paranoid_file_check_enabled) {
|
||||
ASSERT_NOK(s);
|
||||
ASSERT_EQ(Status::Corruption("Paranoid checksums do not match"), s);
|
||||
} else {
|
||||
// CompactRange() goes through if paranoid file check is not enabled
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
|
||||
ASSERT_GE(my_cs->GetCompactionNum(), comp_num + 1);
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
|
||||
// On the worker side, the compaction is considered success
|
||||
// Verification is done on the primary side
|
||||
CompactionServiceResult result;
|
||||
my_cs->GetResult(&result);
|
||||
ASSERT_OK(result.status);
|
||||
ASSERT_TRUE(result.stats.is_manual_compaction);
|
||||
ASSERT_TRUE(result.stats.is_remote_compaction);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(CompactionServiceTest, TruncatedOutput) {
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
ReopenWithCompactionService(&options);
|
||||
GenerateTestData();
|
||||
|
||||
auto my_cs = GetCompactionService();
|
||||
|
||||
std::string start_str = Key(15);
|
||||
std::string end_str = Key(45);
|
||||
Slice start(start_str);
|
||||
Slice end(end_str);
|
||||
uint64_t comp_num = my_cs->GetCompactionNum();
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"CompactionServiceCompactionJob::Run:0", [&](void* arg) {
|
||||
CompactionServiceResult* compaction_result =
|
||||
*(static_cast<CompactionServiceResult**>(arg));
|
||||
ASSERT_TRUE(compaction_result != nullptr &&
|
||||
!compaction_result->output_files.empty());
|
||||
// Truncate files here
|
||||
for (const auto& output_file : compaction_result->output_files) {
|
||||
std::string file_name =
|
||||
compaction_result->output_path + "/" + output_file.file_name;
|
||||
|
||||
uint64_t file_size = 0;
|
||||
Status s = options.env->GetFileSize(file_name, &file_size);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_GT(file_size, 0);
|
||||
|
||||
ASSERT_OK(test::TruncateFile(env_, file_name, file_size / 2));
|
||||
}
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
// CompactRange() should fail
|
||||
Status s = db_->CompactRange(CompactRangeOptions(), &start, &end);
|
||||
ASSERT_NOK(s);
|
||||
ASSERT_TRUE(s.IsCorruption());
|
||||
|
||||
ASSERT_GE(my_cs->GetCompactionNum(), comp_num + 1);
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
|
||||
// On the worker side, the compaction is considered success
|
||||
// Verification is done on the primary side
|
||||
CompactionServiceResult result;
|
||||
my_cs->GetResult(&result);
|
||||
ASSERT_OK(result.status);
|
||||
ASSERT_TRUE(result.stats.is_manual_compaction);
|
||||
ASSERT_TRUE(result.stats.is_remote_compaction);
|
||||
}
|
||||
|
||||
TEST_F(CompactionServiceTest, CustomFileChecksum) {
|
||||
Options options = CurrentOptions();
|
||||
options.file_checksum_gen_factory = GetFileChecksumGenCrc32cFactory();
|
||||
ReopenWithCompactionService(&options);
|
||||
GenerateTestData();
|
||||
|
||||
auto my_cs = GetCompactionService();
|
||||
|
||||
std::string start_str = Key(15);
|
||||
std::string end_str = Key(45);
|
||||
Slice start(start_str);
|
||||
Slice end(end_str);
|
||||
uint64_t comp_num = my_cs->GetCompactionNum();
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"CompactionServiceCompactionJob::Run:0", [&](void* arg) {
|
||||
CompactionServiceResult* compaction_result =
|
||||
*(static_cast<CompactionServiceResult**>(arg));
|
||||
ASSERT_TRUE(compaction_result != nullptr &&
|
||||
!compaction_result->output_files.empty());
|
||||
// Validate Checksum files here
|
||||
for (const auto& output_file : compaction_result->output_files) {
|
||||
std::string file_name =
|
||||
compaction_result->output_path + "/" + output_file.file_name;
|
||||
|
||||
FileChecksumGenContext gen_context;
|
||||
gen_context.file_name = file_name;
|
||||
std::unique_ptr<FileChecksumGenerator> file_checksum_gen =
|
||||
options.file_checksum_gen_factory->CreateFileChecksumGenerator(
|
||||
gen_context);
|
||||
|
||||
std::unique_ptr<SequentialFile> file_reader;
|
||||
uint64_t file_size = 0;
|
||||
Status s = options.env->GetFileSize(file_name, &file_size);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_GT(file_size, 0);
|
||||
|
||||
s = options.env->NewSequentialFile(file_name, &file_reader,
|
||||
EnvOptions());
|
||||
ASSERT_OK(s);
|
||||
|
||||
Slice result;
|
||||
std::unique_ptr<char[]> scratch(new char[file_size]);
|
||||
s = file_reader->Read(file_size, &result, scratch.get());
|
||||
ASSERT_OK(s);
|
||||
|
||||
file_checksum_gen->Update(scratch.get(), result.size());
|
||||
file_checksum_gen->Finalize();
|
||||
|
||||
// Verify actual checksum and the func name
|
||||
ASSERT_EQ(file_checksum_gen->Name(),
|
||||
output_file.file_checksum_func_name);
|
||||
ASSERT_EQ(file_checksum_gen->GetChecksum(),
|
||||
output_file.file_checksum);
|
||||
}
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), &start, &end));
|
||||
ASSERT_GE(my_cs->GetCompactionNum(), comp_num + 1);
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
|
||||
CompactionServiceResult result;
|
||||
my_cs->GetResult(&result);
|
||||
ASSERT_OK(result.status);
|
||||
ASSERT_TRUE(result.stats.is_manual_compaction);
|
||||
ASSERT_TRUE(result.stats.is_remote_compaction);
|
||||
}
|
||||
|
||||
TEST_F(CompactionServiceTest, CancelCompactionOnRemoteSide) {
|
||||
@@ -601,11 +1141,20 @@ TEST_F(CompactionServiceTest, CompactionInfo) {
|
||||
{file.db_path + "/" + file.name}, 2));
|
||||
info = my_cs->GetCompactionInfoForStart();
|
||||
ASSERT_EQ(Env::USER, info.priority);
|
||||
ASSERT_EQ(CompactionReason::kManualCompaction, info.compaction_reason);
|
||||
ASSERT_EQ(true, info.is_manual_compaction);
|
||||
ASSERT_EQ(false, info.is_full_compaction);
|
||||
ASSERT_EQ(true, info.bottommost_level);
|
||||
info = my_cs->GetCompactionInfoForWait();
|
||||
ASSERT_EQ(Env::USER, info.priority);
|
||||
ASSERT_EQ(CompactionReason::kManualCompaction, info.compaction_reason);
|
||||
ASSERT_EQ(true, info.is_manual_compaction);
|
||||
ASSERT_EQ(false, info.is_full_compaction);
|
||||
ASSERT_EQ(true, info.bottommost_level);
|
||||
|
||||
// Test priority BOTTOM
|
||||
env_->SetBackgroundThreads(1, Env::BOTTOM);
|
||||
// This will set bottommost_level = true but is_full_compaction = false
|
||||
options.num_levels = 2;
|
||||
ReopenWithCompactionService(&options);
|
||||
my_cs =
|
||||
@@ -628,9 +1177,71 @@ TEST_F(CompactionServiceTest, CompactionInfo) {
|
||||
}
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
info = my_cs->GetCompactionInfoForStart();
|
||||
ASSERT_EQ(CompactionReason::kLevelL0FilesNum, info.compaction_reason);
|
||||
ASSERT_EQ(false, info.is_manual_compaction);
|
||||
ASSERT_EQ(false, info.is_full_compaction);
|
||||
ASSERT_EQ(true, info.bottommost_level);
|
||||
ASSERT_EQ(Env::BOTTOM, info.priority);
|
||||
info = my_cs->GetCompactionInfoForWait();
|
||||
ASSERT_EQ(Env::BOTTOM, info.priority);
|
||||
ASSERT_EQ(CompactionReason::kLevelL0FilesNum, info.compaction_reason);
|
||||
ASSERT_EQ(false, info.is_manual_compaction);
|
||||
ASSERT_EQ(false, info.is_full_compaction);
|
||||
ASSERT_EQ(true, info.bottommost_level);
|
||||
|
||||
// Test Non-Bottommost Level
|
||||
options.num_levels = 4;
|
||||
ReopenWithCompactionService(&options);
|
||||
my_cs =
|
||||
static_cast_with_check<MyTestCompactionService>(GetCompactionService());
|
||||
|
||||
for (int i = 0; i < options.level0_file_num_compaction_trigger; i++) {
|
||||
for (int j = 0; j < 10; j++) {
|
||||
int key_id = i * 10 + j;
|
||||
ASSERT_OK(Put(Key(key_id), "value_new_new" + std::to_string(key_id)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
info = my_cs->GetCompactionInfoForStart();
|
||||
ASSERT_EQ(false, info.is_manual_compaction);
|
||||
ASSERT_EQ(false, info.is_full_compaction);
|
||||
ASSERT_EQ(false, info.bottommost_level);
|
||||
info = my_cs->GetCompactionInfoForWait();
|
||||
ASSERT_EQ(false, info.is_manual_compaction);
|
||||
ASSERT_EQ(false, info.is_full_compaction);
|
||||
ASSERT_EQ(false, info.bottommost_level);
|
||||
|
||||
// Test Full Compaction + Bottommost Level
|
||||
options.num_levels = 6;
|
||||
ReopenWithCompactionService(&options);
|
||||
my_cs =
|
||||
static_cast_with_check<MyTestCompactionService>(GetCompactionService());
|
||||
|
||||
for (int i = 0; i < 20; i++) {
|
||||
for (int j = 0; j < 10; j++) {
|
||||
int key_id = i * 10 + j;
|
||||
ASSERT_OK(Put(Key(key_id), "value_new_new" + std::to_string(key_id)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
|
||||
CompactRangeOptions cro;
|
||||
cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
|
||||
ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
|
||||
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
info = my_cs->GetCompactionInfoForStart();
|
||||
ASSERT_EQ(true, info.is_manual_compaction);
|
||||
ASSERT_EQ(true, info.is_full_compaction);
|
||||
ASSERT_EQ(true, info.bottommost_level);
|
||||
ASSERT_EQ(CompactionReason::kManualCompaction, info.compaction_reason);
|
||||
info = my_cs->GetCompactionInfoForWait();
|
||||
ASSERT_EQ(true, info.is_manual_compaction);
|
||||
ASSERT_EQ(true, info.is_full_compaction);
|
||||
ASSERT_EQ(true, info.bottommost_level);
|
||||
ASSERT_EQ(CompactionReason::kManualCompaction, info.compaction_reason);
|
||||
}
|
||||
|
||||
TEST_F(CompactionServiceTest, FallbackLocalAuto) {
|
||||
|
||||
@@ -39,7 +39,7 @@ void CompactionState::AggregateCompactionStats(
|
||||
InternalStats::CompactionStatsFull& compaction_stats,
|
||||
CompactionJobStats& compaction_job_stats) {
|
||||
for (const auto& sc : sub_compact_states) {
|
||||
sc.AggregateCompactionStats(compaction_stats);
|
||||
sc.AggregateCompactionOutputStats(compaction_stats);
|
||||
compaction_job_stats.Add(sc.compaction_job_stats);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
#include "rocksdb/sst_partitioner.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
void SubcompactionState::AggregateCompactionStats(
|
||||
void SubcompactionState::AggregateCompactionOutputStats(
|
||||
InternalStats::CompactionStatsFull& compaction_stats) const {
|
||||
compaction_stats.stats.Add(compaction_outputs_.stats_);
|
||||
if (HasPenultimateLevelOutputs()) {
|
||||
|
||||
@@ -179,7 +179,7 @@ class SubcompactionState {
|
||||
|
||||
void Cleanup(Cache* cache);
|
||||
|
||||
void AggregateCompactionStats(
|
||||
void AggregateCompactionOutputStats(
|
||||
InternalStats::CompactionStatsFull& compaction_stats) const;
|
||||
|
||||
CompactionOutputs& Current() const {
|
||||
|
||||
@@ -2512,6 +2512,7 @@ TEST_P(IteratorWriteTimeTest, ReadFromMemtables) {
|
||||
start_time + kSecondsPerRecording * (i + 1));
|
||||
}
|
||||
}
|
||||
ASSERT_EQ(kNumKeys, i);
|
||||
ASSERT_OK(iter->status());
|
||||
}
|
||||
|
||||
@@ -2531,12 +2532,13 @@ TEST_P(IteratorWriteTimeTest, ReadFromMemtables) {
|
||||
}
|
||||
}
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ(-1, i);
|
||||
}
|
||||
|
||||
// Reopen the DB and disable the seqno to time recording, data with user
|
||||
// specified write time can still get a write time before it's flushed.
|
||||
options.preserve_internal_time_seconds = 0;
|
||||
DestroyAndReopen(options);
|
||||
Reopen(options);
|
||||
ASSERT_OK(TimedPut(Key(kKeyWithWriteTime), rnd.RandomString(100),
|
||||
kUserSpecifiedWriteTime));
|
||||
{
|
||||
@@ -2613,6 +2615,7 @@ TEST_P(IteratorWriteTimeTest, ReadFromSstFile) {
|
||||
}
|
||||
}
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ(kNumKeys, i);
|
||||
}
|
||||
|
||||
// Backward iteration
|
||||
@@ -2632,12 +2635,13 @@ TEST_P(IteratorWriteTimeTest, ReadFromSstFile) {
|
||||
}
|
||||
}
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ(-1, i);
|
||||
}
|
||||
|
||||
// Reopen the DB and disable the seqno to time recording. Data retrieved from
|
||||
// SST files still have write time available.
|
||||
options.preserve_internal_time_seconds = 0;
|
||||
DestroyAndReopen(options);
|
||||
Reopen(options);
|
||||
|
||||
dbfull()->TEST_WaitForPeriodicTaskRun(
|
||||
[&] { mock_clock_->MockSleepForSeconds(kSecondsPerRecording); });
|
||||
@@ -2663,6 +2667,7 @@ TEST_P(IteratorWriteTimeTest, ReadFromSstFile) {
|
||||
start_time + kSecondsPerRecording * (i + 1));
|
||||
}
|
||||
}
|
||||
ASSERT_EQ(kNumKeys, i);
|
||||
ASSERT_OK(iter->status());
|
||||
}
|
||||
|
||||
@@ -2686,6 +2691,7 @@ TEST_P(IteratorWriteTimeTest, ReadFromSstFile) {
|
||||
VerifyKeyAndWriteTime(iter.get(), Key(i), 0);
|
||||
}
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ(kNumKeys, i);
|
||||
}
|
||||
Close();
|
||||
}
|
||||
|
||||
+89
-65
@@ -688,76 +688,100 @@ TEST_F(DBBasicTest, IdentityAcrossRestarts) {
|
||||
constexpr size_t kMinIdSize = 10;
|
||||
do {
|
||||
for (bool with_manifest : {false, true}) {
|
||||
std::string idfilename = IdentityFileName(dbname_);
|
||||
std::string id1, tmp;
|
||||
ASSERT_OK(db_->GetDbIdentity(id1));
|
||||
ASSERT_GE(id1.size(), kMinIdSize);
|
||||
for (bool write_file : {false, true}) {
|
||||
std::string idfilename = IdentityFileName(dbname_);
|
||||
std::string id1, tmp;
|
||||
ASSERT_OK(db_->GetDbIdentity(id1));
|
||||
ASSERT_GE(id1.size(), kMinIdSize);
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.write_dbid_to_manifest = with_manifest;
|
||||
Reopen(options);
|
||||
std::string id2;
|
||||
ASSERT_OK(db_->GetDbIdentity(id2));
|
||||
// id2 should match id1 because identity was not regenerated
|
||||
ASSERT_EQ(id1, id2);
|
||||
ASSERT_OK(ReadFileToString(env_, idfilename, &tmp));
|
||||
ASSERT_EQ(tmp, id2);
|
||||
Options options = CurrentOptions();
|
||||
options.write_dbid_to_manifest = with_manifest;
|
||||
options.write_identity_file = true; // initially
|
||||
Reopen(options);
|
||||
std::string id2;
|
||||
ASSERT_OK(db_->GetDbIdentity(id2));
|
||||
// id2 should match id1 because identity was not regenerated
|
||||
ASSERT_EQ(id1, id2);
|
||||
ASSERT_OK(ReadFileToString(env_, idfilename, &tmp));
|
||||
ASSERT_EQ(tmp, id2);
|
||||
|
||||
// Recover from deleted/missing IDENTITY
|
||||
ASSERT_OK(env_->DeleteFile(idfilename));
|
||||
Reopen(options);
|
||||
std::string id3;
|
||||
ASSERT_OK(db_->GetDbIdentity(id3));
|
||||
if (with_manifest) {
|
||||
// id3 should match id1 because identity was restored from manifest
|
||||
ASSERT_EQ(id1, id3);
|
||||
} else {
|
||||
// id3 should NOT match id1 because identity was regenerated
|
||||
ASSERT_NE(id1, id3);
|
||||
ASSERT_GE(id3.size(), kMinIdSize);
|
||||
}
|
||||
ASSERT_OK(ReadFileToString(env_, idfilename, &tmp));
|
||||
ASSERT_EQ(tmp, id3);
|
||||
if (write_file) {
|
||||
// Recover from deleted/missing IDENTITY
|
||||
ASSERT_OK(env_->DeleteFile(idfilename));
|
||||
} else {
|
||||
// Transition to no IDENTITY file
|
||||
options.write_identity_file = false;
|
||||
if (!with_manifest) {
|
||||
// Incompatible options, should fail
|
||||
ASSERT_NOK(TryReopen(options));
|
||||
// Back to a usable config and continue
|
||||
options.write_identity_file = true;
|
||||
Reopen(options);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
Reopen(options);
|
||||
std::string id3;
|
||||
ASSERT_OK(db_->GetDbIdentity(id3));
|
||||
if (with_manifest) {
|
||||
// id3 should match id1 because identity was restored from manifest
|
||||
ASSERT_EQ(id1, id3);
|
||||
} else {
|
||||
// id3 should NOT match id1 because identity was regenerated
|
||||
ASSERT_NE(id1, id3);
|
||||
ASSERT_GE(id3.size(), kMinIdSize);
|
||||
}
|
||||
if (write_file) {
|
||||
ASSERT_OK(ReadFileToString(env_, idfilename, &tmp));
|
||||
ASSERT_EQ(tmp, id3);
|
||||
|
||||
// Recover from truncated IDENTITY
|
||||
{
|
||||
std::unique_ptr<WritableFile> w;
|
||||
ASSERT_OK(env_->NewWritableFile(idfilename, &w, EnvOptions()));
|
||||
ASSERT_OK(w->Close());
|
||||
}
|
||||
Reopen(options);
|
||||
std::string id4;
|
||||
ASSERT_OK(db_->GetDbIdentity(id4));
|
||||
if (with_manifest) {
|
||||
// id4 should match id1 because identity was restored from manifest
|
||||
ASSERT_EQ(id1, id4);
|
||||
} else {
|
||||
// id4 should NOT match id1 because identity was regenerated
|
||||
ASSERT_NE(id1, id4);
|
||||
ASSERT_GE(id4.size(), kMinIdSize);
|
||||
}
|
||||
ASSERT_OK(ReadFileToString(env_, idfilename, &tmp));
|
||||
ASSERT_EQ(tmp, id4);
|
||||
// Recover from truncated IDENTITY
|
||||
std::unique_ptr<WritableFile> w;
|
||||
ASSERT_OK(env_->NewWritableFile(idfilename, &w, EnvOptions()));
|
||||
ASSERT_OK(w->Close());
|
||||
} else {
|
||||
ASSERT_TRUE(env_->FileExists(idfilename).IsNotFound());
|
||||
}
|
||||
Reopen(options);
|
||||
std::string id4;
|
||||
ASSERT_OK(db_->GetDbIdentity(id4));
|
||||
if (with_manifest) {
|
||||
// id4 should match id1 because identity was restored from manifest
|
||||
ASSERT_EQ(id1, id4);
|
||||
} else {
|
||||
// id4 should NOT match id1 because identity was regenerated
|
||||
ASSERT_NE(id1, id4);
|
||||
ASSERT_GE(id4.size(), kMinIdSize);
|
||||
}
|
||||
std::string silly_id = "asdf123456789";
|
||||
if (write_file) {
|
||||
ASSERT_OK(ReadFileToString(env_, idfilename, &tmp));
|
||||
ASSERT_EQ(tmp, id4);
|
||||
|
||||
// Recover from overwritten IDENTITY
|
||||
std::string silly_id = "asdf123456789";
|
||||
{
|
||||
std::unique_ptr<WritableFile> w;
|
||||
ASSERT_OK(env_->NewWritableFile(idfilename, &w, EnvOptions()));
|
||||
ASSERT_OK(w->Append(silly_id));
|
||||
ASSERT_OK(w->Close());
|
||||
// Recover from overwritten IDENTITY
|
||||
std::unique_ptr<WritableFile> w;
|
||||
ASSERT_OK(env_->NewWritableFile(idfilename, &w, EnvOptions()));
|
||||
ASSERT_OK(w->Append(silly_id));
|
||||
ASSERT_OK(w->Close());
|
||||
} else {
|
||||
ASSERT_TRUE(env_->FileExists(idfilename).IsNotFound());
|
||||
}
|
||||
Reopen(options);
|
||||
std::string id5;
|
||||
ASSERT_OK(db_->GetDbIdentity(id5));
|
||||
if (with_manifest) {
|
||||
// id4 should match id1 because identity was restored from manifest
|
||||
ASSERT_EQ(id1, id5);
|
||||
} else {
|
||||
ASSERT_EQ(id5, silly_id);
|
||||
}
|
||||
if (write_file) {
|
||||
ASSERT_OK(ReadFileToString(env_, idfilename, &tmp));
|
||||
ASSERT_EQ(tmp, id5);
|
||||
} else {
|
||||
ASSERT_TRUE(env_->FileExists(idfilename).IsNotFound());
|
||||
}
|
||||
}
|
||||
Reopen(options);
|
||||
std::string id5;
|
||||
ASSERT_OK(db_->GetDbIdentity(id5));
|
||||
if (with_manifest) {
|
||||
// id4 should match id1 because identity was restored from manifest
|
||||
ASSERT_EQ(id1, id5);
|
||||
} else {
|
||||
ASSERT_EQ(id5, silly_id);
|
||||
}
|
||||
ASSERT_OK(ReadFileToString(env_, idfilename, &tmp));
|
||||
ASSERT_EQ(tmp, id5);
|
||||
}
|
||||
} while (ChangeCompactOptions());
|
||||
}
|
||||
|
||||
+353
-89
@@ -171,7 +171,7 @@ TEST_P(DBBloomFilterTestDefFormatVersion, KeyMayExist) {
|
||||
options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
|
||||
ASSERT_TRUE(!db_->KeyMayExist(ropts, handles_[1], "a", &value));
|
||||
ASSERT_FALSE(db_->KeyMayExist(ropts, handles_[1], "a", &value));
|
||||
|
||||
ASSERT_OK(Put(1, "a", "b"));
|
||||
bool value_found = false;
|
||||
@@ -187,7 +187,7 @@ TEST_P(DBBloomFilterTestDefFormatVersion, KeyMayExist) {
|
||||
uint64_t cache_added = TestGetTickerCount(options, BLOCK_CACHE_ADD);
|
||||
ASSERT_TRUE(
|
||||
db_->KeyMayExist(ropts, handles_[1], "a", &value, &value_found));
|
||||
ASSERT_TRUE(!value_found);
|
||||
ASSERT_FALSE(value_found);
|
||||
// assert that no new files were opened and no new blocks were
|
||||
// read into block cache.
|
||||
ASSERT_EQ(numopen, TestGetTickerCount(options, NO_FILE_OPENS));
|
||||
@@ -197,7 +197,7 @@ TEST_P(DBBloomFilterTestDefFormatVersion, KeyMayExist) {
|
||||
|
||||
numopen = TestGetTickerCount(options, NO_FILE_OPENS);
|
||||
cache_added = TestGetTickerCount(options, BLOCK_CACHE_ADD);
|
||||
ASSERT_TRUE(!db_->KeyMayExist(ropts, handles_[1], "a", &value));
|
||||
ASSERT_FALSE(db_->KeyMayExist(ropts, handles_[1], "a", &value));
|
||||
ASSERT_EQ(numopen, TestGetTickerCount(options, NO_FILE_OPENS));
|
||||
ASSERT_EQ(cache_added, TestGetTickerCount(options, BLOCK_CACHE_ADD));
|
||||
|
||||
@@ -207,7 +207,7 @@ TEST_P(DBBloomFilterTestDefFormatVersion, KeyMayExist) {
|
||||
|
||||
numopen = TestGetTickerCount(options, NO_FILE_OPENS);
|
||||
cache_added = TestGetTickerCount(options, BLOCK_CACHE_ADD);
|
||||
ASSERT_TRUE(!db_->KeyMayExist(ropts, handles_[1], "a", &value));
|
||||
ASSERT_FALSE(db_->KeyMayExist(ropts, handles_[1], "a", &value));
|
||||
ASSERT_EQ(numopen, TestGetTickerCount(options, NO_FILE_OPENS));
|
||||
ASSERT_EQ(cache_added, TestGetTickerCount(options, BLOCK_CACHE_ADD));
|
||||
|
||||
@@ -215,7 +215,7 @@ TEST_P(DBBloomFilterTestDefFormatVersion, KeyMayExist) {
|
||||
|
||||
numopen = TestGetTickerCount(options, NO_FILE_OPENS);
|
||||
cache_added = TestGetTickerCount(options, BLOCK_CACHE_ADD);
|
||||
ASSERT_TRUE(!db_->KeyMayExist(ropts, handles_[1], "c", &value));
|
||||
ASSERT_FALSE(db_->KeyMayExist(ropts, handles_[1], "c", &value));
|
||||
ASSERT_EQ(numopen, TestGetTickerCount(options, NO_FILE_OPENS));
|
||||
ASSERT_EQ(cache_added, TestGetTickerCount(options, BLOCK_CACHE_ADD));
|
||||
|
||||
@@ -2177,24 +2177,146 @@ TEST_F(DBBloomFilterTest, MemtableWholeKeyBloomFilterMultiGet) {
|
||||
|
||||
db_->ReleaseSnapshot(snapshot);
|
||||
}
|
||||
namespace {
|
||||
std::pair<uint64_t, uint64_t> GetBloomStat(const Options& options, bool sst) {
|
||||
if (sst) {
|
||||
return {options.statistics->getAndResetTickerCount(
|
||||
NON_LAST_LEVEL_SEEK_FILTER_MATCH),
|
||||
options.statistics->getAndResetTickerCount(
|
||||
NON_LAST_LEVEL_SEEK_FILTERED)};
|
||||
} else {
|
||||
auto hit = std::exchange(get_perf_context()->bloom_memtable_hit_count, 0);
|
||||
auto miss = std::exchange(get_perf_context()->bloom_memtable_miss_count, 0);
|
||||
return {hit, miss};
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBBloomFilterTest, MemtablePrefixBloomOutOfDomain) {
|
||||
constexpr size_t kPrefixSize = 8;
|
||||
const std::string kKey = "key";
|
||||
assert(kKey.size() < kPrefixSize);
|
||||
std::pair<uint64_t, uint64_t> HitAndMiss(uint64_t hits, uint64_t misses) {
|
||||
return {hits, misses};
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_F(DBBloomFilterTest, MemtablePrefixBloom) {
|
||||
Options options = CurrentOptions();
|
||||
options.prefix_extractor.reset(NewFixedPrefixTransform(kPrefixSize));
|
||||
options.prefix_extractor.reset(NewFixedPrefixTransform(4));
|
||||
options.memtable_prefix_bloom_size_ratio = 0.25;
|
||||
Reopen(options);
|
||||
ASSERT_OK(Put(kKey, "v"));
|
||||
ASSERT_EQ("v", Get(kKey));
|
||||
std::unique_ptr<Iterator> iter(dbfull()->NewIterator(ReadOptions()));
|
||||
iter->Seek(kKey);
|
||||
ASSERT_FALSE(options.prefix_extractor->InDomain("key"));
|
||||
ASSERT_OK(Put("key", "v"));
|
||||
ASSERT_OK(Put("goat1", "g1"));
|
||||
ASSERT_OK(Put("goat2", "g2"));
|
||||
|
||||
// Reset from other tests
|
||||
GetBloomStat(options, false);
|
||||
|
||||
// Out of domain (Get)
|
||||
ASSERT_EQ("v", Get("key"));
|
||||
ASSERT_EQ(HitAndMiss(0, 0), GetBloomStat(options, false));
|
||||
|
||||
// In domain (Get)
|
||||
ASSERT_EQ("g1", Get("goat1"));
|
||||
ASSERT_EQ(HitAndMiss(1, 0), GetBloomStat(options, false));
|
||||
ASSERT_EQ("NOT_FOUND", Get("goat9"));
|
||||
ASSERT_EQ(HitAndMiss(1, 0), GetBloomStat(options, false));
|
||||
ASSERT_EQ("NOT_FOUND", Get("goan1"));
|
||||
ASSERT_EQ(HitAndMiss(0, 1), GetBloomStat(options, false));
|
||||
|
||||
ReadOptions ropts;
|
||||
if (options.prefix_seek_opt_in_only) {
|
||||
ropts.prefix_same_as_start = true;
|
||||
}
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(ropts));
|
||||
// Out of domain (scan)
|
||||
iter->Seek("ke");
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ(kKey, iter->key());
|
||||
iter->SeekForPrev(kKey);
|
||||
ASSERT_EQ("key", iter->key());
|
||||
iter->SeekForPrev("kez");
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ(kKey, iter->key());
|
||||
ASSERT_EQ("key", iter->key());
|
||||
ASSERT_EQ(HitAndMiss(0, 0), GetBloomStat(options, false));
|
||||
|
||||
// In domain (scan)
|
||||
iter->Seek("goan");
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_FALSE(iter->Valid());
|
||||
ASSERT_EQ(HitAndMiss(0, 1), GetBloomStat(options, false));
|
||||
iter->Seek("goat");
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ("goat1", iter->key());
|
||||
ASSERT_EQ(HitAndMiss(1, 0), GetBloomStat(options, false));
|
||||
|
||||
// Changing prefix extractor should affect prefix query semantics
|
||||
// and bypass the existing memtable Bloom filter
|
||||
ASSERT_OK(db_->SetOptions({{"prefix_extractor", "fixed:5"}}));
|
||||
iter.reset(db_->NewIterator(ropts));
|
||||
// Now out of domain (scan)
|
||||
iter->Seek("goan");
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ("goat1", iter->key());
|
||||
ASSERT_EQ(HitAndMiss(0, 0), GetBloomStat(options, false));
|
||||
// In domain (scan)
|
||||
iter->Seek("goat2");
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ("goat2", iter->key());
|
||||
ASSERT_EQ(HitAndMiss(0, 0), GetBloomStat(options, false));
|
||||
// In domain (scan)
|
||||
if (ropts.prefix_same_as_start) {
|
||||
iter->Seek("goat0");
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_FALSE(iter->Valid());
|
||||
ASSERT_EQ(HitAndMiss(0, 0), GetBloomStat(options, false));
|
||||
} else {
|
||||
// NOTE: legacy prefix Seek may return keys outside of prefix
|
||||
}
|
||||
|
||||
// Start a fresh new memtable, using new prefix extractor
|
||||
ASSERT_OK(SingleDelete("key"));
|
||||
ASSERT_OK(SingleDelete("goat1"));
|
||||
ASSERT_OK(SingleDelete("goat2"));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
ASSERT_OK(Put("key", "_v"));
|
||||
ASSERT_OK(Put("goat1", "_g1"));
|
||||
ASSERT_OK(Put("goat2", "_g2"));
|
||||
|
||||
iter.reset(db_->NewIterator(ropts));
|
||||
|
||||
// Still out of domain (Get)
|
||||
ASSERT_EQ("_v", Get("key"));
|
||||
ASSERT_EQ(HitAndMiss(0, 0), GetBloomStat(options, false));
|
||||
|
||||
// Still in domain (Get)
|
||||
ASSERT_EQ("_g1", Get("goat1"));
|
||||
ASSERT_EQ(HitAndMiss(1, 0), GetBloomStat(options, false));
|
||||
ASSERT_EQ("NOT_FOUND", Get("goat11"));
|
||||
ASSERT_EQ(HitAndMiss(1, 0), GetBloomStat(options, false));
|
||||
ASSERT_EQ("NOT_FOUND", Get("goat9"));
|
||||
ASSERT_EQ(HitAndMiss(0, 1), GetBloomStat(options, false));
|
||||
ASSERT_EQ("NOT_FOUND", Get("goan1"));
|
||||
ASSERT_EQ(HitAndMiss(0, 1), GetBloomStat(options, false));
|
||||
|
||||
// Now out of domain (scan)
|
||||
iter->Seek("goan");
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ("goat1", iter->key());
|
||||
ASSERT_EQ(HitAndMiss(0, 0), GetBloomStat(options, false));
|
||||
// In domain (scan)
|
||||
iter->Seek("goat2");
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ("goat2", iter->key());
|
||||
ASSERT_EQ(HitAndMiss(1, 0), GetBloomStat(options, false));
|
||||
// In domain (scan)
|
||||
iter->Seek("goat0");
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_FALSE(iter->Valid());
|
||||
ASSERT_EQ(HitAndMiss(0, 1), GetBloomStat(options, false));
|
||||
}
|
||||
|
||||
class DBBloomFilterTestVaryPrefixAndFormatVer
|
||||
@@ -2507,7 +2629,11 @@ TEST_P(BloomStatsTestWithParam, BloomStatsTestWithIter) {
|
||||
ASSERT_OK(Put(key1, value1, WriteOptions()));
|
||||
ASSERT_OK(Put(key3, value3, WriteOptions()));
|
||||
|
||||
std::unique_ptr<Iterator> iter(dbfull()->NewIterator(ReadOptions()));
|
||||
ReadOptions ropts;
|
||||
if (options_.prefix_seek_opt_in_only) {
|
||||
ropts.prefix_same_as_start = true;
|
||||
}
|
||||
std::unique_ptr<Iterator> iter(dbfull()->NewIterator(ropts));
|
||||
|
||||
// check memtable bloom stats
|
||||
iter->Seek(key1);
|
||||
@@ -2526,13 +2652,13 @@ TEST_P(BloomStatsTestWithParam, BloomStatsTestWithIter) {
|
||||
|
||||
iter->Seek(key2);
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_TRUE(!iter->Valid());
|
||||
ASSERT_FALSE(iter->Valid());
|
||||
ASSERT_EQ(1, get_perf_context()->bloom_memtable_miss_count);
|
||||
ASSERT_EQ(2, get_perf_context()->bloom_memtable_hit_count);
|
||||
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
iter.reset(dbfull()->NewIterator(ReadOptions()));
|
||||
iter.reset(dbfull()->NewIterator(ropts));
|
||||
|
||||
// Check SST bloom stats
|
||||
iter->Seek(key1);
|
||||
@@ -2550,7 +2676,7 @@ TEST_P(BloomStatsTestWithParam, BloomStatsTestWithIter) {
|
||||
|
||||
iter->Seek(key2);
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_TRUE(!iter->Valid());
|
||||
ASSERT_FALSE(iter->Valid());
|
||||
ASSERT_EQ(1, get_perf_context()->bloom_sst_miss_count);
|
||||
ASSERT_EQ(expected_hits, get_perf_context()->bloom_sst_hit_count);
|
||||
}
|
||||
@@ -2659,9 +2785,14 @@ TEST_F(DBBloomFilterTest, PrefixScan) {
|
||||
PrefixScanInit(this);
|
||||
count = 0;
|
||||
env_->random_read_counter_.Reset();
|
||||
iter = db_->NewIterator(ReadOptions());
|
||||
ReadOptions ropts;
|
||||
if (options.prefix_seek_opt_in_only) {
|
||||
ropts.prefix_same_as_start = true;
|
||||
}
|
||||
iter = db_->NewIterator(ropts);
|
||||
for (iter->Seek(prefix); iter->Valid(); iter->Next()) {
|
||||
if (!iter->key().starts_with(prefix)) {
|
||||
ASSERT_FALSE(ropts.prefix_same_as_start);
|
||||
break;
|
||||
}
|
||||
count++;
|
||||
@@ -3397,23 +3528,6 @@ class FixedSuffix4Transform : public SliceTransform {
|
||||
|
||||
bool InDomain(const Slice& src) const override { return src.size() >= 4; }
|
||||
};
|
||||
|
||||
std::pair<uint64_t, uint64_t> GetBloomStat(const Options& options, bool sst) {
|
||||
if (sst) {
|
||||
return {options.statistics->getAndResetTickerCount(
|
||||
NON_LAST_LEVEL_SEEK_FILTER_MATCH),
|
||||
options.statistics->getAndResetTickerCount(
|
||||
NON_LAST_LEVEL_SEEK_FILTERED)};
|
||||
} else {
|
||||
auto hit = std::exchange(get_perf_context()->bloom_memtable_hit_count, 0);
|
||||
auto miss = std::exchange(get_perf_context()->bloom_memtable_miss_count, 0);
|
||||
return {hit, miss};
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<uint64_t, uint64_t> HitAndMiss(uint64_t hits, uint64_t misses) {
|
||||
return {hits, misses};
|
||||
}
|
||||
} // anonymous namespace
|
||||
|
||||
// This uses a prefix_extractor + comparator combination that violates
|
||||
@@ -3520,9 +3634,8 @@ TEST_F(DBBloomFilterTest, WeirdPrefixExtractorWithFilter2) {
|
||||
if (flushed) { // TODO: support auto_prefix_mode in memtable?
|
||||
read_options.auto_prefix_mode = true;
|
||||
} else {
|
||||
// TODO: why needed?
|
||||
get_perf_context()->bloom_memtable_hit_count = 0;
|
||||
get_perf_context()->bloom_memtable_miss_count = 0;
|
||||
// Reset from other tests
|
||||
GetBloomStat(options, flushed);
|
||||
}
|
||||
EXPECT_EQ(GetBloomStat(options, flushed), HitAndMiss(0, 0));
|
||||
{
|
||||
@@ -3664,9 +3777,8 @@ TEST_F(DBBloomFilterTest, WeirdPrefixExtractorWithFilter3) {
|
||||
if (flushed) { // TODO: support auto_prefix_mode in memtable?
|
||||
read_options.auto_prefix_mode = true;
|
||||
} else {
|
||||
// TODO: why needed?
|
||||
get_perf_context()->bloom_memtable_hit_count = 0;
|
||||
get_perf_context()->bloom_memtable_miss_count = 0;
|
||||
// Reset from other tests
|
||||
GetBloomStat(options, flushed);
|
||||
}
|
||||
EXPECT_EQ(GetBloomStat(options, flushed), HitAndMiss(0, 0));
|
||||
{
|
||||
@@ -3734,14 +3846,33 @@ TEST_F(DBBloomFilterTest, WeirdPrefixExtractorWithFilter3) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBBloomFilterTest, SstQueryFilter) {
|
||||
using experimental::KeySegmentsExtractor;
|
||||
using experimental::MakeSharedBytewiseMinMaxSQFC;
|
||||
using experimental::SelectKeySegment;
|
||||
using experimental::SstQueryFilterConfigs;
|
||||
using experimental::SstQueryFilterConfigsManager;
|
||||
using KeyCategorySet = KeySegmentsExtractor::KeyCategorySet;
|
||||
using experimental::KeySegmentsExtractor;
|
||||
using experimental::MakeSharedBytewiseMinMaxSQFC;
|
||||
using experimental::MakeSharedCappedKeySegmentsExtractor;
|
||||
using experimental::MakeSharedReverseBytewiseMinMaxSQFC;
|
||||
using experimental::SelectKeySegment;
|
||||
using experimental::SstQueryFilterConfigs;
|
||||
using experimental::SstQueryFilterConfigsManager;
|
||||
using KeyCategorySet = KeySegmentsExtractor::KeyCategorySet;
|
||||
|
||||
static std::vector<std::string> RangeQueryKeys(
|
||||
SstQueryFilterConfigsManager::Factory& factory, DB& db, const Slice& lb,
|
||||
const Slice& ub) {
|
||||
ReadOptions ro;
|
||||
ro.iterate_lower_bound = &lb;
|
||||
ro.iterate_upper_bound = &ub;
|
||||
ro.table_filter = factory.GetTableFilterForRangeQuery(lb, ub);
|
||||
auto it = db.NewIterator(ro);
|
||||
std::vector<std::string> ret;
|
||||
for (it->Seek(lb); it->Valid(); it->Next()) {
|
||||
ret.push_back(it->key().ToString());
|
||||
}
|
||||
EXPECT_OK(it->status());
|
||||
delete it;
|
||||
return ret;
|
||||
};
|
||||
|
||||
TEST_F(DBBloomFilterTest, SstQueryFilter) {
|
||||
struct MySegmentExtractor : public KeySegmentsExtractor {
|
||||
char min_first_char;
|
||||
char max_first_char;
|
||||
@@ -3890,101 +4021,86 @@ TEST_F(DBBloomFilterTest, SstQueryFilter) {
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
using Keys = std::vector<std::string>;
|
||||
auto RangeQueryKeys =
|
||||
auto RangeQuery =
|
||||
[factory, db = db_](
|
||||
std::string lb, std::string ub,
|
||||
std::shared_ptr<SstQueryFilterConfigsManager::Factory> alt_factory =
|
||||
nullptr) {
|
||||
Slice lb_slice = lb;
|
||||
Slice ub_slice = ub;
|
||||
|
||||
ReadOptions ro;
|
||||
ro.iterate_lower_bound = &lb_slice;
|
||||
ro.iterate_upper_bound = &ub_slice;
|
||||
ro.table_filter = (alt_factory ? alt_factory : factory)
|
||||
->GetTableFilterForRangeQuery(lb_slice, ub_slice);
|
||||
auto it = db->NewIterator(ro);
|
||||
Keys ret;
|
||||
for (it->Seek(lb_slice); it->Valid(); it->Next()) {
|
||||
ret.push_back(it->key().ToString());
|
||||
}
|
||||
EXPECT_OK(it->status());
|
||||
delete it;
|
||||
return ret;
|
||||
return RangeQueryKeys(alt_factory ? *alt_factory : *factory, *db, lb,
|
||||
ub);
|
||||
};
|
||||
|
||||
// Control 1: range is not filtered but min/max filter is checked
|
||||
// because of common prefix leading up to 2nd segment
|
||||
// TODO/future: statistics for when filter is checked vs. not applicable
|
||||
EXPECT_EQ(RangeQueryKeys("abc_150", "abc_249"),
|
||||
EXPECT_EQ(RangeQuery("abc_150", "abc_249"),
|
||||
Keys({"abc_156_987", "abc_234", "abc_245_567"}));
|
||||
EXPECT_EQ(TestGetAndResetTickerCount(options, NON_LAST_LEVEL_SEEK_DATA), 2);
|
||||
|
||||
// Test 1: range is filtered to just lowest level, fully containing the
|
||||
// segments in that category
|
||||
EXPECT_EQ(RangeQueryKeys("abc_100", "abc_179"),
|
||||
EXPECT_EQ(RangeQuery("abc_100", "abc_179"),
|
||||
Keys({"abc_123", "abc_13", "abc_156_987"}));
|
||||
EXPECT_EQ(TestGetAndResetTickerCount(options, NON_LAST_LEVEL_SEEK_DATA), 1);
|
||||
|
||||
// Test 2: range is filtered to just lowest level, partial overlap
|
||||
EXPECT_EQ(RangeQueryKeys("abc_1500_x_y", "abc_16QQ"), Keys({"abc_156_987"}));
|
||||
EXPECT_EQ(RangeQuery("abc_1500_x_y", "abc_16QQ"), Keys({"abc_156_987"}));
|
||||
EXPECT_EQ(TestGetAndResetTickerCount(options, NON_LAST_LEVEL_SEEK_DATA), 1);
|
||||
|
||||
// Test 3: range is filtered to just highest level, fully containing the
|
||||
// segments in that category but would be overlapping the range for the other
|
||||
// file if the filter included all categories
|
||||
EXPECT_EQ(RangeQueryKeys("abc_200", "abc_300"),
|
||||
EXPECT_EQ(RangeQuery("abc_200", "abc_300"),
|
||||
Keys({"abc_234", "abc_245_567", "abc_25"}));
|
||||
EXPECT_EQ(TestGetAndResetTickerCount(options, NON_LAST_LEVEL_SEEK_DATA), 1);
|
||||
|
||||
// Test 4: range is filtered to just highest level, partial overlap (etc.)
|
||||
EXPECT_EQ(RangeQueryKeys("abc_200", "abc_249"),
|
||||
Keys({"abc_234", "abc_245_567"}));
|
||||
EXPECT_EQ(RangeQuery("abc_200", "abc_249"), Keys({"abc_234", "abc_245_567"}));
|
||||
EXPECT_EQ(TestGetAndResetTickerCount(options, NON_LAST_LEVEL_SEEK_DATA), 1);
|
||||
|
||||
// Test 5: range is filtered from both levels, because of category scope
|
||||
EXPECT_EQ(RangeQueryKeys("abc_300", "abc_400"), Keys({}));
|
||||
EXPECT_EQ(RangeQuery("abc_300", "abc_400"), Keys({}));
|
||||
EXPECT_EQ(TestGetAndResetTickerCount(options, NON_LAST_LEVEL_SEEK_DATA), 0);
|
||||
|
||||
// Control 2: range is not filtered because association between 1st and
|
||||
// 2nd segment is not represented
|
||||
EXPECT_EQ(RangeQueryKeys("abc_170", "abc_190"), Keys({}));
|
||||
EXPECT_EQ(RangeQuery("abc_170", "abc_190"), Keys({}));
|
||||
EXPECT_EQ(TestGetAndResetTickerCount(options, NON_LAST_LEVEL_SEEK_DATA), 2);
|
||||
|
||||
// Control 3: range is not filtered because there's no (bloom) filter on
|
||||
// 1st segment (like prefix filtering)
|
||||
EXPECT_EQ(RangeQueryKeys("baa_170", "baa_190"), Keys({}));
|
||||
EXPECT_EQ(RangeQuery("baa_170", "baa_190"), Keys({}));
|
||||
EXPECT_EQ(TestGetAndResetTickerCount(options, NON_LAST_LEVEL_SEEK_DATA), 2);
|
||||
|
||||
// Control 4: range is not filtered because difference in segments leading
|
||||
// up to 2nd segment
|
||||
EXPECT_EQ(RangeQueryKeys("abc_500", "abd_501"), Keys({}));
|
||||
EXPECT_EQ(RangeQuery("abc_500", "abd_501"), Keys({}));
|
||||
EXPECT_EQ(TestGetAndResetTickerCount(options, NON_LAST_LEVEL_SEEK_DATA), 2);
|
||||
|
||||
// TODO: exclusive upper bound tests
|
||||
|
||||
// ======= Testing 3rd segment (cross-category filter) =======
|
||||
// Control 5: not filtered because of segment range overlap
|
||||
EXPECT_EQ(RangeQueryKeys(" z__700", " z__750"), Keys({}));
|
||||
EXPECT_EQ(RangeQuery(" z__700", " z__750"), Keys({}));
|
||||
EXPECT_EQ(TestGetAndResetTickerCount(options, NON_LAST_LEVEL_SEEK_DATA), 2);
|
||||
|
||||
// Test 6: filtered on both levels
|
||||
EXPECT_EQ(RangeQueryKeys(" z__100", " z__300"), Keys({}));
|
||||
EXPECT_EQ(RangeQuery(" z__100", " z__300"), Keys({}));
|
||||
EXPECT_EQ(TestGetAndResetTickerCount(options, NON_LAST_LEVEL_SEEK_DATA), 0);
|
||||
|
||||
// Control 6: finding something, with 2nd segment filter helping
|
||||
EXPECT_EQ(RangeQueryKeys("abc_156_9", "abc_156_99"), Keys({"abc_156_987"}));
|
||||
EXPECT_EQ(RangeQuery("abc_156_9", "abc_156_99"), Keys({"abc_156_987"}));
|
||||
EXPECT_EQ(TestGetAndResetTickerCount(options, NON_LAST_LEVEL_SEEK_DATA), 1);
|
||||
|
||||
EXPECT_EQ(RangeQueryKeys("abc_245_56", "abc_245_57"), Keys({"abc_245_567"}));
|
||||
EXPECT_EQ(RangeQuery("abc_245_56", "abc_245_57"), Keys({"abc_245_567"}));
|
||||
EXPECT_EQ(TestGetAndResetTickerCount(options, NON_LAST_LEVEL_SEEK_DATA), 1);
|
||||
|
||||
// Test 6: filtered on both levels, for different segments
|
||||
EXPECT_EQ(RangeQueryKeys("abc_245_900", "abc_245_999"), Keys({}));
|
||||
EXPECT_EQ(RangeQuery("abc_245_900", "abc_245_999"), Keys({}));
|
||||
EXPECT_EQ(TestGetAndResetTickerCount(options, NON_LAST_LEVEL_SEEK_DATA), 0);
|
||||
|
||||
// ======= Testing extractor read portability =======
|
||||
EXPECT_EQ(RangeQueryKeys("abc_300", "abc_400"), Keys({}));
|
||||
EXPECT_EQ(RangeQuery("abc_300", "abc_400"), Keys({}));
|
||||
EXPECT_EQ(TestGetAndResetTickerCount(options, NON_LAST_LEVEL_SEEK_DATA), 0);
|
||||
|
||||
// Only modifies how filters are written
|
||||
@@ -3992,20 +4108,168 @@ TEST_F(DBBloomFilterTest, SstQueryFilter) {
|
||||
ASSERT_EQ(factory->GetFilteringVersion(), 0U);
|
||||
ASSERT_EQ(factory->GetConfigs().IsEmptyNotFound(), true);
|
||||
|
||||
EXPECT_EQ(RangeQueryKeys("abc_300", "abc_400"), Keys({}));
|
||||
EXPECT_EQ(RangeQuery("abc_300", "abc_400"), Keys({}));
|
||||
EXPECT_EQ(TestGetAndResetTickerCount(options, NON_LAST_LEVEL_SEEK_DATA), 0);
|
||||
|
||||
// Even a different config name with different extractor can read
|
||||
EXPECT_EQ(RangeQueryKeys("abc_300", "abc_400", MakeFactory("bar", 43)),
|
||||
Keys({}));
|
||||
EXPECT_EQ(RangeQuery("abc_300", "abc_400", MakeFactory("bar", 43)), Keys({}));
|
||||
EXPECT_EQ(TestGetAndResetTickerCount(options, NON_LAST_LEVEL_SEEK_DATA), 0);
|
||||
|
||||
// Or a "not found" config name
|
||||
EXPECT_EQ(RangeQueryKeys("abc_300", "abc_400", MakeFactory("blah", 43)),
|
||||
EXPECT_EQ(RangeQuery("abc_300", "abc_400", MakeFactory("blah", 43)),
|
||||
Keys({}));
|
||||
EXPECT_EQ(TestGetAndResetTickerCount(options, NON_LAST_LEVEL_SEEK_DATA), 0);
|
||||
}
|
||||
|
||||
static std::vector<int> ExtractedSizes(const KeySegmentsExtractor& ex,
|
||||
const Slice& k) {
|
||||
KeySegmentsExtractor::Result r;
|
||||
ex.Extract(k, KeySegmentsExtractor::kFullUserKey, &r);
|
||||
std::vector<int> ret;
|
||||
uint32_t last = 0;
|
||||
for (const auto i : r.segment_ends) {
|
||||
ret.push_back(static_cast<int>(i - last));
|
||||
last = i;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
TEST_F(DBBloomFilterTest, FixedWidthSegments) {
|
||||
// Unit tests for
|
||||
auto extractor_none = MakeSharedCappedKeySegmentsExtractor({});
|
||||
auto extractor0b = MakeSharedCappedKeySegmentsExtractor({0});
|
||||
auto extractor1b = MakeSharedCappedKeySegmentsExtractor({1});
|
||||
auto extractor4b = MakeSharedCappedKeySegmentsExtractor({4});
|
||||
auto extractor4b0b = MakeSharedCappedKeySegmentsExtractor({4, 0});
|
||||
auto extractor4b0b4b = MakeSharedCappedKeySegmentsExtractor({4, 0, 4});
|
||||
auto extractor1b3b0b4b = MakeSharedCappedKeySegmentsExtractor({1, 3, 0, 4});
|
||||
|
||||
ASSERT_EQ(extractor_none->GetId(), "CappedKeySegmentsExtractor");
|
||||
ASSERT_EQ(extractor0b->GetId(), "CappedKeySegmentsExtractor0b");
|
||||
ASSERT_EQ(extractor1b->GetId(), "CappedKeySegmentsExtractor1b");
|
||||
ASSERT_EQ(extractor4b->GetId(), "CappedKeySegmentsExtractor4b");
|
||||
ASSERT_EQ(extractor4b0b->GetId(), "CappedKeySegmentsExtractor4b0b");
|
||||
ASSERT_EQ(extractor4b0b4b->GetId(), "CappedKeySegmentsExtractor4b0b4b");
|
||||
ASSERT_EQ(extractor1b3b0b4b->GetId(), "CappedKeySegmentsExtractor1b3b0b4b");
|
||||
|
||||
using V = std::vector<int>;
|
||||
ASSERT_EQ(V({}), ExtractedSizes(*extractor_none, {}));
|
||||
ASSERT_EQ(V({0}), ExtractedSizes(*extractor0b, {}));
|
||||
ASSERT_EQ(V({0}), ExtractedSizes(*extractor1b, {}));
|
||||
ASSERT_EQ(V({0}), ExtractedSizes(*extractor4b, {}));
|
||||
ASSERT_EQ(V({0, 0}), ExtractedSizes(*extractor4b0b, {}));
|
||||
ASSERT_EQ(V({0, 0, 0}), ExtractedSizes(*extractor4b0b4b, {}));
|
||||
ASSERT_EQ(V({0, 0, 0, 0}), ExtractedSizes(*extractor1b3b0b4b, {}));
|
||||
|
||||
ASSERT_EQ(V({3}), ExtractedSizes(*extractor4b, "bla"));
|
||||
ASSERT_EQ(V({3, 0}), ExtractedSizes(*extractor4b0b, "bla"));
|
||||
ASSERT_EQ(V({1, 2, 0, 0}), ExtractedSizes(*extractor1b3b0b4b, "bla"));
|
||||
|
||||
ASSERT_EQ(V({}), ExtractedSizes(*extractor_none, "blah"));
|
||||
ASSERT_EQ(V({0}), ExtractedSizes(*extractor0b, "blah"));
|
||||
ASSERT_EQ(V({1}), ExtractedSizes(*extractor1b, "blah"));
|
||||
ASSERT_EQ(V({4}), ExtractedSizes(*extractor4b, "blah"));
|
||||
ASSERT_EQ(V({4, 0}), ExtractedSizes(*extractor4b0b, "blah"));
|
||||
ASSERT_EQ(V({4, 0, 0}), ExtractedSizes(*extractor4b0b4b, "blah"));
|
||||
ASSERT_EQ(V({1, 3, 0, 0}), ExtractedSizes(*extractor1b3b0b4b, "blah"));
|
||||
|
||||
ASSERT_EQ(V({4, 0}), ExtractedSizes(*extractor4b0b, "blah1"));
|
||||
ASSERT_EQ(V({4, 0, 1}), ExtractedSizes(*extractor4b0b4b, "blah1"));
|
||||
ASSERT_EQ(V({4, 0, 2}), ExtractedSizes(*extractor4b0b4b, "blah12"));
|
||||
ASSERT_EQ(V({4, 0, 3}), ExtractedSizes(*extractor4b0b4b, "blah123"));
|
||||
ASSERT_EQ(V({1, 3, 0, 3}), ExtractedSizes(*extractor1b3b0b4b, "blah123"));
|
||||
|
||||
ASSERT_EQ(V({4}), ExtractedSizes(*extractor4b, "blah1234"));
|
||||
ASSERT_EQ(V({4, 0}), ExtractedSizes(*extractor4b0b, "blah1234"));
|
||||
ASSERT_EQ(V({4, 0, 4}), ExtractedSizes(*extractor4b0b4b, "blah1234"));
|
||||
ASSERT_EQ(V({1, 3, 0, 4}), ExtractedSizes(*extractor1b3b0b4b, "blah1234"));
|
||||
|
||||
ASSERT_EQ(V({4, 0, 4}), ExtractedSizes(*extractor4b0b4b, "blah12345"));
|
||||
ASSERT_EQ(V({1, 3, 0, 4}), ExtractedSizes(*extractor1b3b0b4b, "blah12345"));
|
||||
|
||||
// Filter config for second and fourth segment
|
||||
auto filter1 =
|
||||
MakeSharedReverseBytewiseMinMaxSQFC(experimental::SelectKeySegment(1));
|
||||
auto filter3 =
|
||||
MakeSharedReverseBytewiseMinMaxSQFC(experimental::SelectKeySegment(3));
|
||||
SstQueryFilterConfigs configs1 = {{filter1, filter3}, extractor1b3b0b4b};
|
||||
SstQueryFilterConfigsManager::Data data = {{42, {{"foo", configs1}}}};
|
||||
std::shared_ptr<SstQueryFilterConfigsManager> configs_manager;
|
||||
ASSERT_OK(SstQueryFilterConfigsManager::MakeShared(data, &configs_manager));
|
||||
std::shared_ptr<SstQueryFilterConfigsManager::Factory> f;
|
||||
ASSERT_OK(configs_manager->MakeSharedFactory("foo", 42, &f));
|
||||
|
||||
ASSERT_EQ(f->GetConfigsName(), "foo");
|
||||
ASSERT_EQ(f->GetConfigs().IsEmptyNotFound(), false);
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.statistics = CreateDBStatistics();
|
||||
options.table_properties_collector_factories.push_back(f);
|
||||
// Next most common comparator after bytewise
|
||||
options.comparator = ReverseBytewiseComparator();
|
||||
|
||||
DestroyAndReopen(options);
|
||||
|
||||
ASSERT_OK(Put("abcd1234", "val0"));
|
||||
ASSERT_OK(Put("abcd1245", "val1"));
|
||||
ASSERT_OK(Put("abcd99", "val2")); // short key
|
||||
ASSERT_OK(Put("aqua1200", "val3"));
|
||||
ASSERT_OK(Put("aqua1230", "val4"));
|
||||
ASSERT_OK(Put("zen", "val5")); // very short key
|
||||
ASSERT_OK(Put("azur1220", "val6"));
|
||||
ASSERT_OK(Put("blah", "val7"));
|
||||
ASSERT_OK(Put("blah2", "val8"));
|
||||
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
using Keys = std::vector<std::string>;
|
||||
|
||||
// Range is not filtered but segment 1 min/max filter is checked
|
||||
EXPECT_EQ(RangeQueryKeys(*f, *db_, "aczz0000", "acdc0000"), Keys({}));
|
||||
EXPECT_EQ(1, TestGetAndResetTickerCount(options, NON_LAST_LEVEL_SEEK_DATA));
|
||||
|
||||
// Found (can't use segment 3 filter)
|
||||
EXPECT_EQ(RangeQueryKeys(*f, *db_, "aqzz0000", "aqdc0000"),
|
||||
Keys({"aqua1230", "aqua1200"}));
|
||||
EXPECT_EQ(1, TestGetAndResetTickerCount(options, NON_LAST_LEVEL_SEEK_DATA));
|
||||
|
||||
// Filtered because of segment 1 min-max not intersecting [aaa, abb]
|
||||
EXPECT_EQ(RangeQueryKeys(*f, *db_, "zabb9999", "zaaa0000"), Keys({}));
|
||||
EXPECT_EQ(0, TestGetAndResetTickerCount(options, NON_LAST_LEVEL_SEEK_DATA));
|
||||
|
||||
// Found
|
||||
EXPECT_EQ(RangeQueryKeys(*f, *db_, "aqua1200ZZZ", "aqua1000ZZZ"),
|
||||
Keys({"aqua1200"}));
|
||||
EXPECT_EQ(1, TestGetAndResetTickerCount(options, NON_LAST_LEVEL_SEEK_DATA));
|
||||
|
||||
// Found despite short key
|
||||
EXPECT_EQ(RangeQueryKeys(*f, *db_, "aqua121", "aqua1"), Keys({"aqua1200"}));
|
||||
EXPECT_EQ(1, TestGetAndResetTickerCount(options, NON_LAST_LEVEL_SEEK_DATA));
|
||||
|
||||
// Filtered because of segment 3 min-max not intersecting [1000, 1100]
|
||||
// Note that the empty string is tracked outside of the min-max range.
|
||||
EXPECT_EQ(RangeQueryKeys(*f, *db_, "aqua1100ZZZ", "aqua1000ZZZ"), Keys({}));
|
||||
EXPECT_EQ(0, TestGetAndResetTickerCount(options, NON_LAST_LEVEL_SEEK_DATA));
|
||||
|
||||
// Also filtered despite short key
|
||||
EXPECT_EQ(RangeQueryKeys(*f, *db_, "aqua11", "aqua1"), Keys({}));
|
||||
EXPECT_EQ(0, TestGetAndResetTickerCount(options, NON_LAST_LEVEL_SEEK_DATA));
|
||||
|
||||
// Found
|
||||
EXPECT_EQ(RangeQueryKeys(*f, *db_, "blah21", "blag"),
|
||||
Keys({"blah2", "blah"}));
|
||||
EXPECT_EQ(1, TestGetAndResetTickerCount(options, NON_LAST_LEVEL_SEEK_DATA));
|
||||
|
||||
// Found
|
||||
EXPECT_EQ(RangeQueryKeys(*f, *db_, "blah0", "blag"), Keys({"blah"}));
|
||||
EXPECT_EQ(1, TestGetAndResetTickerCount(options, NON_LAST_LEVEL_SEEK_DATA));
|
||||
|
||||
// Filtered because of segment 3 min-max not intersecting [0, 1]
|
||||
// Note that the empty string is tracked outside of the min-max range.
|
||||
EXPECT_EQ(RangeQueryKeys(*f, *db_, "blah1", "blah0"), Keys({}));
|
||||
EXPECT_EQ(0, TestGetAndResetTickerCount(options, NON_LAST_LEVEL_SEEK_DATA));
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -6146,7 +6146,7 @@ TEST_F(DBCompactionTest, CompactionLimiter) {
|
||||
|
||||
std::vector<std::string> pending_compaction_cfs;
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"SchedulePendingCompaction::cfd", [&](void* arg) {
|
||||
"EnqueuePendingCompaction::cfd", [&](void* arg) {
|
||||
const std::string& cf_name =
|
||||
static_cast<ColumnFamilyData*>(arg)->GetName();
|
||||
pending_compaction_cfs.emplace_back(cf_name);
|
||||
@@ -9357,12 +9357,13 @@ TEST_F(DBCompactionTest, FIFOChangeTemperature) {
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
ASSERT_OK(Put(Key(0), "value1"));
|
||||
env_->MockSleepForSeconds(800);
|
||||
ASSERT_OK(Put(Key(2), "value2"));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
// First two L0 files both become eligible for temperature change compaction
|
||||
// They should be compacted one-by-one.
|
||||
ASSERT_OK(Put(Key(0), "value1"));
|
||||
env_->MockSleepForSeconds(800);
|
||||
env_->MockSleepForSeconds(1200);
|
||||
ASSERT_OK(Put(Key(2), "value2"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
|
||||
+119
-16
@@ -473,7 +473,7 @@ Status DBImpl::ResumeImpl(DBRecoverContext context) {
|
||||
|
||||
if (s.ok()) {
|
||||
for (auto cfd : *versions_->GetColumnFamilySet()) {
|
||||
SchedulePendingCompaction(cfd);
|
||||
EnqueuePendingCompaction(cfd);
|
||||
}
|
||||
MaybeScheduleFlushOrCompaction();
|
||||
}
|
||||
@@ -530,6 +530,11 @@ Status DBImpl::MaybeReleaseTimestampedSnapshotsAndCheck() {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
void DBImpl::UntrackDataFiles() {
|
||||
TrackOrUntrackFiles(/*existing_data_files=*/{},
|
||||
/*track=*/false);
|
||||
}
|
||||
|
||||
Status DBImpl::CloseHelper() {
|
||||
// Guarantee that there is no background error recovery in progress before
|
||||
// continuing with the shutdown
|
||||
@@ -654,8 +659,9 @@ Status DBImpl::CloseHelper() {
|
||||
// We need to release them before the block cache is destroyed. The block
|
||||
// cache may be destroyed inside versions_.reset(), when column family data
|
||||
// list is destroyed, so leaving handles in table cache after
|
||||
// versions_.reset() may cause issues.
|
||||
// Here we clean all unreferenced handles in table cache.
|
||||
// versions_.reset() may cause issues. Here we clean all unreferenced handles
|
||||
// in table cache, and (for certain builds/conditions) assert that no obsolete
|
||||
// files are hanging around unreferenced (leak) in the table/blob file cache.
|
||||
// Now we assume all user queries have finished, so only version set itself
|
||||
// can possibly hold the blocks from block cache. After releasing unreferenced
|
||||
// handles here, only handles held by version set left and inside
|
||||
@@ -663,12 +669,22 @@ Status DBImpl::CloseHelper() {
|
||||
// time a handle is released, we erase it from the cache too. By doing that,
|
||||
// we can guarantee that after versions_.reset(), table cache is empty
|
||||
// so the cache can be safely destroyed.
|
||||
#ifndef NDEBUG
|
||||
TEST_VerifyNoObsoleteFilesCached(/*db_mutex_already_held=*/true);
|
||||
#endif // !NDEBUG
|
||||
table_cache_->EraseUnRefEntries();
|
||||
|
||||
for (auto& txn_entry : recovered_transactions_) {
|
||||
delete txn_entry.second;
|
||||
}
|
||||
|
||||
// Return an unowned SstFileManager to a consistent state
|
||||
if (immutable_db_options_.sst_file_manager && !own_sfm_) {
|
||||
mutex_.Unlock();
|
||||
UntrackDataFiles();
|
||||
mutex_.Lock();
|
||||
}
|
||||
|
||||
// versions need to be destroyed before table_cache since it can hold
|
||||
// references to table_cache.
|
||||
{
|
||||
@@ -1526,7 +1542,7 @@ Status DBImpl::FlushWAL(const WriteOptions& write_options, bool sync) {
|
||||
io_s.ToString().c_str());
|
||||
// In case there is a fs error we should set it globally to prevent the
|
||||
// future writes
|
||||
IOStatusCheck(io_s);
|
||||
WALIOStatusCheck(io_s);
|
||||
// whether sync or not, we should abort the rest of function upon error
|
||||
return static_cast<Status>(io_s);
|
||||
}
|
||||
@@ -1683,7 +1699,7 @@ 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
|
||||
IOStatusCheck(io_s);
|
||||
WALIOStatusCheck(io_s);
|
||||
}
|
||||
if (io_s.ok() && need_wal_dir_sync) {
|
||||
io_s = directories_.GetWalDir()->FsyncWithDirOptions(
|
||||
@@ -2054,15 +2070,19 @@ InternalIterator* DBImpl::NewInternalIterator(
|
||||
bool allow_unprepared_value, ArenaWrappedDBIter* db_iter) {
|
||||
InternalIterator* internal_iter;
|
||||
assert(arena != nullptr);
|
||||
auto prefix_extractor =
|
||||
super_version->mutable_cf_options.prefix_extractor.get();
|
||||
// Need to create internal iterator from the arena.
|
||||
MergeIteratorBuilder merge_iter_builder(
|
||||
&cfd->internal_comparator(), arena,
|
||||
!read_options.total_order_seek &&
|
||||
super_version->mutable_cf_options.prefix_extractor != nullptr,
|
||||
// FIXME? It's not clear what interpretation of prefix seek is needed
|
||||
// here, and no unit test cares about the value provided here.
|
||||
!read_options.total_order_seek && prefix_extractor != nullptr,
|
||||
read_options.iterate_upper_bound);
|
||||
// Collect iterator for mutable memtable
|
||||
auto mem_iter = super_version->mem->NewIterator(
|
||||
read_options, super_version->GetSeqnoToTimeMapping(), arena);
|
||||
read_options, super_version->GetSeqnoToTimeMapping(), arena,
|
||||
super_version->mutable_cf_options.prefix_extractor.get());
|
||||
Status s;
|
||||
if (!read_options.ignore_range_deletions) {
|
||||
std::unique_ptr<TruncatedRangeDelIterator> mem_tombstone_iter;
|
||||
@@ -2086,6 +2106,7 @@ InternalIterator* DBImpl::NewInternalIterator(
|
||||
if (s.ok()) {
|
||||
super_version->imm->AddIterators(
|
||||
read_options, super_version->GetSeqnoToTimeMapping(),
|
||||
super_version->mutable_cf_options.prefix_extractor.get(),
|
||||
&merge_iter_builder, !read_options.ignore_range_deletions);
|
||||
}
|
||||
TEST_SYNC_POINT_CALLBACK("DBImpl::NewInternalIterator:StatusCallback", &s);
|
||||
@@ -3143,10 +3164,11 @@ Status DBImpl::MultiGetImpl(
|
||||
StopWatch sw(immutable_db_options_.clock, stats_, DB_MULTIGET);
|
||||
|
||||
assert(sorted_keys);
|
||||
assert(start_key + num_keys <= sorted_keys->size());
|
||||
// Clear the timestamps for returning results so that we can distinguish
|
||||
// between tombstone or key that has never been written
|
||||
for (auto* kctx : *sorted_keys) {
|
||||
assert(kctx);
|
||||
for (size_t i = start_key; i < start_key + num_keys; ++i) {
|
||||
KeyContext* kctx = (*sorted_keys)[i];
|
||||
if (kctx->timestamp) {
|
||||
kctx->timestamp->clear();
|
||||
}
|
||||
@@ -3209,6 +3231,8 @@ Status DBImpl::MultiGetImpl(
|
||||
s = Status::Aborted();
|
||||
break;
|
||||
}
|
||||
// This could be a long-running operation
|
||||
ROCKSDB_THREAD_YIELD_HOOK();
|
||||
}
|
||||
|
||||
// Post processing (decrement reference counts and record statistics)
|
||||
@@ -3830,6 +3854,9 @@ Iterator* DBImpl::NewIterator(const ReadOptions& _read_options,
|
||||
}
|
||||
}
|
||||
if (read_options.tailing) {
|
||||
read_options.total_order_seek |=
|
||||
immutable_db_options_.prefix_seek_opt_in_only;
|
||||
|
||||
auto iter = new ForwardIterator(this, read_options, cfd, sv,
|
||||
/* allow_unprepared_value */ true);
|
||||
result = NewDBIterator(
|
||||
@@ -4031,6 +4058,9 @@ Status DBImpl::NewIterators(
|
||||
|
||||
assert(cf_sv_pairs.size() == column_families.size());
|
||||
if (read_options.tailing) {
|
||||
read_options.total_order_seek |=
|
||||
immutable_db_options_.prefix_seek_opt_in_only;
|
||||
|
||||
for (const auto& cf_sv_pair : cf_sv_pairs) {
|
||||
auto iter = new ForwardIterator(this, read_options, cf_sv_pair.cfd,
|
||||
cf_sv_pair.super_version,
|
||||
@@ -4281,7 +4311,7 @@ void DBImpl::ReleaseSnapshot(const Snapshot* s) {
|
||||
->storage_info()
|
||||
->BottommostFilesMarkedForCompaction()
|
||||
.empty()) {
|
||||
SchedulePendingCompaction(cfd);
|
||||
EnqueuePendingCompaction(cfd);
|
||||
MaybeScheduleFlushOrCompaction();
|
||||
cf_scheduled.push_back(cfd);
|
||||
}
|
||||
@@ -4755,6 +4785,24 @@ void DBImpl::ReleaseFileNumberFromPendingOutputs(
|
||||
}
|
||||
}
|
||||
|
||||
std::list<uint64_t>::iterator DBImpl::CaptureOptionsFileNumber() {
|
||||
// We need to remember the iterator of our insert, because after the
|
||||
// compaction is done, we need to remove that element from
|
||||
// min_options_file_numbers_.
|
||||
min_options_file_numbers_.push_back(versions_->options_file_number());
|
||||
auto min_options_file_numbers_inserted_elem = min_options_file_numbers_.end();
|
||||
--min_options_file_numbers_inserted_elem;
|
||||
return min_options_file_numbers_inserted_elem;
|
||||
}
|
||||
|
||||
void DBImpl::ReleaseOptionsFileNumber(
|
||||
std::unique_ptr<std::list<uint64_t>::iterator>& v) {
|
||||
if (v.get() != nullptr) {
|
||||
min_options_file_numbers_.erase(*v.get());
|
||||
v.reset();
|
||||
}
|
||||
}
|
||||
|
||||
Status DBImpl::GetUpdatesSince(
|
||||
SequenceNumber seq, std::unique_ptr<TransactionLogIterator>* iter,
|
||||
const TransactionLogIterator::ReadOptions& read_options) {
|
||||
@@ -5835,11 +5883,10 @@ Status DBImpl::IngestExternalFiles(
|
||||
"write_global_seqno is deprecated and does not work with "
|
||||
"allow_db_generated_files.");
|
||||
}
|
||||
if (ingest_opts.move_files) {
|
||||
return Status::NotSupported(
|
||||
"Options move_files and allow_db_generated_files are not "
|
||||
"compatible.");
|
||||
}
|
||||
}
|
||||
if (ingest_opts.move_files && ingest_opts.link_files) {
|
||||
return Status::InvalidArgument(
|
||||
"`move_files` and `link_files` can not both be true.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6747,6 +6794,62 @@ void DBImpl::RecordSeqnoToTimeMapping(uint64_t populate_historical_seconds) {
|
||||
}
|
||||
}
|
||||
|
||||
void DBImpl::TrackOrUntrackFiles(
|
||||
const std::vector<std::string>& existing_data_files, bool track) {
|
||||
auto sfm = static_cast_with_check<SstFileManagerImpl>(
|
||||
immutable_db_options_.sst_file_manager.get());
|
||||
assert(sfm);
|
||||
std::vector<ColumnFamilyMetaData> metadata;
|
||||
GetAllColumnFamilyMetaData(&metadata);
|
||||
auto action = [&](const std::string& file_path,
|
||||
std::optional<uint64_t> size) {
|
||||
if (track) {
|
||||
if (size) {
|
||||
sfm->OnAddFile(file_path, *size).PermitUncheckedError();
|
||||
} else {
|
||||
sfm->OnAddFile(file_path).PermitUncheckedError();
|
||||
}
|
||||
} else {
|
||||
sfm->OnUntrackFile(file_path).PermitUncheckedError();
|
||||
}
|
||||
};
|
||||
|
||||
std::unordered_set<std::string> referenced_files;
|
||||
for (const auto& md : metadata) {
|
||||
for (const auto& lmd : md.levels) {
|
||||
for (const auto& fmd : lmd.files) {
|
||||
// We're assuming that each sst file name exists in at most one of
|
||||
// the paths.
|
||||
std::string file_path =
|
||||
fmd.directory + kFilePathSeparator + fmd.relative_filename;
|
||||
action(file_path, fmd.size);
|
||||
referenced_files.insert(file_path);
|
||||
}
|
||||
}
|
||||
for (const auto& bmd : md.blob_files) {
|
||||
std::string name = bmd.blob_file_name;
|
||||
// The BlobMetaData.blob_file_name may start with "/".
|
||||
if (!name.empty() && name[0] == kFilePathSeparator) {
|
||||
name = name.substr(1);
|
||||
}
|
||||
// We're assuming that each blob file name exists in at most one of
|
||||
// the paths.
|
||||
std::string file_path = bmd.blob_file_path + kFilePathSeparator + name;
|
||||
action(file_path, bmd.blob_file_size);
|
||||
referenced_files.insert(file_path);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& file_path : existing_data_files) {
|
||||
if (referenced_files.find(file_path) != referenced_files.end()) {
|
||||
continue;
|
||||
}
|
||||
// There shouldn't be any duplicated files. In case there is, SstFileManager
|
||||
// will take care of deduping it.
|
||||
action(file_path, /*size=*/std::nullopt);
|
||||
}
|
||||
}
|
||||
|
||||
void DBImpl::InstallSeqnoToTimeMappingInSV(
|
||||
std::vector<SuperVersionContext>* sv_contexts) {
|
||||
mutex_.AssertHeld();
|
||||
|
||||
+68
-9
@@ -853,6 +853,8 @@ class DBImpl : public DB {
|
||||
|
||||
uint64_t GetObsoleteSstFilesSize();
|
||||
|
||||
uint64_t MinOptionsFileNumberToKeep();
|
||||
|
||||
// Returns the list of live files in 'live' and the list
|
||||
// of all files in the filesystem in 'candidate_files'.
|
||||
// If force == false and the last call was less than
|
||||
@@ -1151,6 +1153,8 @@ class DBImpl : public DB {
|
||||
// Get the background error status
|
||||
Status TEST_GetBGError();
|
||||
|
||||
bool TEST_IsRecoveryInProgress();
|
||||
|
||||
// Return the maximum overlapping data (in bytes) at next level for any
|
||||
// file at a level >= 1.
|
||||
uint64_t TEST_MaxNextLevelOverlappingBytes(
|
||||
@@ -1237,9 +1241,14 @@ class DBImpl : public DB {
|
||||
static Status TEST_ValidateOptions(const DBOptions& db_options) {
|
||||
return ValidateOptions(db_options);
|
||||
}
|
||||
|
||||
#endif // NDEBUG
|
||||
|
||||
// In certain configurations, verify that the table/blob file cache only
|
||||
// contains entries for live files, to check for effective leaks of open
|
||||
// files. This can only be called when purging of obsolete files has
|
||||
// "settled," such as during parts of DB Close().
|
||||
void TEST_VerifyNoObsoleteFilesCached(bool db_mutex_already_held) const;
|
||||
|
||||
// persist stats to column family "_persistent_stats"
|
||||
void PersistStats();
|
||||
|
||||
@@ -1582,11 +1591,12 @@ class DBImpl : public DB {
|
||||
|
||||
virtual bool OwnTablesAndLogs() const { return true; }
|
||||
|
||||
// Setup DB identity file, and write DB ID to manifest if necessary.
|
||||
// Read/create DB identity file (as appropriate), and write DB ID to
|
||||
// version_edit if provided.
|
||||
Status SetupDBId(const WriteOptions& write_options, bool read_only,
|
||||
RecoveryContext* recovery_ctx);
|
||||
// Assign db_id_ and write DB ID to manifest if necessary.
|
||||
void SetDBId(std::string&& id, bool read_only, RecoveryContext* recovery_ctx);
|
||||
bool is_new_db, VersionEdit* version_edit);
|
||||
// Assign db_id_ and write DB ID to version_edit if provided.
|
||||
void SetDBId(std::string&& id, bool read_only, VersionEdit* version_edit);
|
||||
|
||||
// Collect a deduplicated collection of paths used by this DB, including
|
||||
// dbname_, DBOptions.db_paths, ColumnFamilyOptions.cf_paths.
|
||||
@@ -1616,9 +1626,15 @@ class DBImpl : public DB {
|
||||
// vast majority of all files), since it already has the file size
|
||||
// on record, we don't need to query the file system. Otherwise, we query the
|
||||
// file system for the size of an unreferenced file.
|
||||
// REQUIRES: mutex unlocked
|
||||
void TrackExistingDataFiles(
|
||||
const std::vector<std::string>& existing_data_files);
|
||||
|
||||
// Untrack data files in sst manager. This is only called during DB::Close on
|
||||
// an unowned SstFileManager, to return it to a consistent state.
|
||||
// REQUIRES: mutex unlocked
|
||||
void UntrackDataFiles();
|
||||
|
||||
// SetDbSessionId() should be called in the constuctor DBImpl()
|
||||
// to ensure that db_session_id_ gets updated every time the DB is opened
|
||||
void SetDbSessionId();
|
||||
@@ -1685,6 +1701,8 @@ class DBImpl : public DB {
|
||||
friend class XFTransactionWriteHandler;
|
||||
friend class DBBlobIndexTest;
|
||||
friend class WriteUnpreparedTransactionTest_RecoveryTest_Test;
|
||||
friend class CompactionServiceTest_PreservedOptionsLocalCompaction_Test;
|
||||
friend class CompactionServiceTest_PreservedOptionsRemoteCompaction_Test;
|
||||
#endif
|
||||
|
||||
struct CompactionState;
|
||||
@@ -1956,6 +1974,13 @@ class DBImpl : public DB {
|
||||
void ReleaseFileNumberFromPendingOutputs(
|
||||
std::unique_ptr<std::list<uint64_t>::iterator>& v);
|
||||
|
||||
// Similar to pending_outputs, preserve OPTIONS file. Used for remote
|
||||
// compaction.
|
||||
std::list<uint64_t>::iterator CaptureOptionsFileNumber();
|
||||
void ReleaseOptionsFileNumber(
|
||||
std::unique_ptr<std::list<uint64_t>::iterator>& v);
|
||||
|
||||
// Sets bg error if there is an error writing to WAL.
|
||||
IOStatus SyncClosedWals(const WriteOptions& write_options,
|
||||
JobContext* job_context, VersionEdit* synced_wals,
|
||||
bool error_recovery_in_prog);
|
||||
@@ -2174,7 +2199,7 @@ 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 IOStatusCheck(const IOStatus& status);
|
||||
void WALIOStatusCheck(const IOStatus& status);
|
||||
|
||||
// Used by WriteImpl to update bg_error_ in case of memtable insert error.
|
||||
void MemTableInsertStatusCheck(const Status& memtable_insert_status);
|
||||
@@ -2187,6 +2212,10 @@ class DBImpl : public DB {
|
||||
JobContext* job_context, LogBuffer* log_buffer,
|
||||
CompactionJobInfo* compaction_job_info);
|
||||
|
||||
// REQUIRES: mutex unlocked
|
||||
void TrackOrUntrackFiles(const std::vector<std::string>& existing_data_files,
|
||||
bool track);
|
||||
|
||||
ColumnFamilyData* GetColumnFamilyDataByName(const std::string& cf_name);
|
||||
|
||||
void MaybeScheduleFlushOrCompaction();
|
||||
@@ -2216,10 +2245,27 @@ class DBImpl : public DB {
|
||||
void GenerateFlushRequest(const autovector<ColumnFamilyData*>& cfds,
|
||||
FlushReason flush_reason, FlushRequest* req);
|
||||
|
||||
// Returns true if `req` is successfully enqueued.
|
||||
bool SchedulePendingFlush(const FlushRequest& req);
|
||||
// Below functions are for executing flush, compaction in the background. A
|
||||
// dequeue is the communication channel between threads that asks for the work
|
||||
// to be done and the available threads in the thread pool that pick it up to
|
||||
// execute it. We use these terminologies to describe the state of the work
|
||||
// and its transitions:
|
||||
// 1) It becomes pending once it's successfully enqueued into the
|
||||
// corresponding dequeue, a work in this state is also called unscheduled.
|
||||
// Counter `unscheduled_*_` counts work in this state.
|
||||
// 2) When `MaybeScheduleFlushOrCompaction` schedule a thread to run `BGWork*`
|
||||
// for the work, it becomes scheduled
|
||||
// Counter `bg_*_scheduled_` counts work in this state.
|
||||
// 3) Once the thread start to execute `BGWork*`, the work is popped from the
|
||||
// dequeue, it is now in running state
|
||||
// Counter `num_running_*_` counts work in this state.
|
||||
// 4) Eventually, the work is finished. We don't need to specifically track
|
||||
// finished work.
|
||||
|
||||
void SchedulePendingCompaction(ColumnFamilyData* cfd);
|
||||
// Returns true if `req` is successfully enqueued.
|
||||
bool EnqueuePendingFlush(const FlushRequest& req);
|
||||
|
||||
void EnqueuePendingCompaction(ColumnFamilyData* cfd);
|
||||
void SchedulePendingPurge(std::string fname, std::string dir_to_sync,
|
||||
FileType type, uint64_t number, int job_id);
|
||||
static void BGWorkCompaction(void* arg);
|
||||
@@ -2724,6 +2770,11 @@ class DBImpl : public DB {
|
||||
// State is protected with db mutex.
|
||||
std::list<uint64_t> pending_outputs_;
|
||||
|
||||
// Similar to pending_outputs_, FindObsoleteFiles()/PurgeObsoleteFiles() never
|
||||
// deletes any OPTIONS file that has number bigger than any of the file number
|
||||
// in min_options_file_numbers_.
|
||||
std::list<uint64_t> min_options_file_numbers_;
|
||||
|
||||
// flush_queue_ and compaction_queue_ hold column families that we need to
|
||||
// flush and compact, respectively.
|
||||
// A column family is inserted into flush_queue_ when it satisfies condition
|
||||
@@ -2946,6 +2997,14 @@ DBOptions SanitizeOptions(const std::string& db, const DBOptions& src,
|
||||
CompressionType GetCompressionFlush(const ImmutableCFOptions& ioptions,
|
||||
const MutableCFOptions& mutable_cf_options);
|
||||
|
||||
// Return a VersionEdit for the DB's recovery when the `memtables` of the
|
||||
// specified column family are obsolete. Specifically, the min log number to
|
||||
// keep, and the WAL files that can be deleted.
|
||||
VersionEdit GetDBRecoveryEditForObsoletingMemTables(
|
||||
VersionSet* vset, const ColumnFamilyData& cfd,
|
||||
const autovector<VersionEdit*>& edit_list,
|
||||
const autovector<MemTable*>& memtables, LogsWithPrepTracker* prep_tracker);
|
||||
|
||||
// Return the earliest log file to keep after the memtable flush is
|
||||
// finalized.
|
||||
// `cfd_to_flush` is the column family whose memtable (specified in
|
||||
|
||||
@@ -1561,6 +1561,12 @@ Status DBImpl::CompactFilesImpl(
|
||||
|
||||
compaction_job.Prepare();
|
||||
|
||||
std::unique_ptr<std::list<uint64_t>::iterator> min_options_file_number_elem;
|
||||
if (immutable_db_options().compaction_service != nullptr) {
|
||||
min_options_file_number_elem.reset(
|
||||
new std::list<uint64_t>::iterator(CaptureOptionsFileNumber()));
|
||||
}
|
||||
|
||||
mutex_.Unlock();
|
||||
TEST_SYNC_POINT("CompactFilesImpl:0");
|
||||
TEST_SYNC_POINT("CompactFilesImpl:1");
|
||||
@@ -1570,6 +1576,10 @@ Status DBImpl::CompactFilesImpl(
|
||||
TEST_SYNC_POINT("CompactFilesImpl:3");
|
||||
mutex_.Lock();
|
||||
|
||||
if (immutable_db_options().compaction_service != nullptr) {
|
||||
ReleaseOptionsFileNumber(min_options_file_number_elem);
|
||||
}
|
||||
|
||||
bool compaction_released = false;
|
||||
Status status =
|
||||
compaction_job.Install(*c->mutable_cf_options(), &compaction_released);
|
||||
@@ -1880,7 +1890,7 @@ Status DBImpl::ReFitLevel(ColumnFamilyData* cfd, int level, int target_level) {
|
||||
Status status = versions_->LogAndApply(cfd, mutable_cf_options,
|
||||
read_options, write_options, &edit,
|
||||
&mutex_, directories_.GetDbDir());
|
||||
|
||||
c->MarkFilesBeingCompacted(false);
|
||||
cfd->compaction_picker()->UnregisterCompaction(c.get());
|
||||
c.reset();
|
||||
|
||||
@@ -2377,7 +2387,7 @@ Status DBImpl::FlushMemTable(ColumnFamilyData* cfd,
|
||||
ColumnFamilyData* loop_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 = SchedulePendingFlush(req);
|
||||
bool flush_req_enqueued = EnqueuePendingFlush(req);
|
||||
if (already_queued_for_flush || flush_req_enqueued) {
|
||||
loop_cfd->SetFlushSkipReschedule();
|
||||
}
|
||||
@@ -2528,7 +2538,7 @@ Status DBImpl::AtomicFlushMemTables(
|
||||
}
|
||||
}
|
||||
GenerateFlushRequest(cfds, flush_reason, &flush_req);
|
||||
SchedulePendingFlush(flush_req);
|
||||
EnqueuePendingFlush(flush_req);
|
||||
MaybeScheduleFlushOrCompaction();
|
||||
}
|
||||
|
||||
@@ -2583,7 +2593,7 @@ Status DBImpl::RetryFlushesForErrorRecovery(FlushReason flush_reason,
|
||||
if (immutable_db_options_.atomic_flush) {
|
||||
FlushRequest flush_req;
|
||||
GenerateFlushRequest(cfds, flush_reason, &flush_req);
|
||||
SchedulePendingFlush(flush_req);
|
||||
EnqueuePendingFlush(flush_req);
|
||||
for (auto& iter : flush_req.cfd_to_max_mem_id_to_persist) {
|
||||
flush_memtable_ids.push_back(iter.second);
|
||||
}
|
||||
@@ -2597,7 +2607,7 @@ Status DBImpl::RetryFlushesForErrorRecovery(FlushReason flush_reason,
|
||||
flush_reason,
|
||||
{{cfd,
|
||||
std::numeric_limits<uint64_t>::max() /* max_mem_id_to_persist */}}};
|
||||
if (SchedulePendingFlush(flush_req)) {
|
||||
if (EnqueuePendingFlush(flush_req)) {
|
||||
cfd->SetFlushSkipReschedule();
|
||||
};
|
||||
}
|
||||
@@ -2950,6 +2960,7 @@ void DBImpl::AddToCompactionQueue(ColumnFamilyData* cfd) {
|
||||
cfd->Ref();
|
||||
compaction_queue_.push_back(cfd);
|
||||
cfd->set_queued_for_compaction(true);
|
||||
++unscheduled_compactions_;
|
||||
}
|
||||
|
||||
ColumnFamilyData* DBImpl::PopFirstFromCompactionQueue() {
|
||||
@@ -3005,7 +3016,7 @@ ColumnFamilyData* DBImpl::PickCompactionFromQueue(
|
||||
return cfd;
|
||||
}
|
||||
|
||||
bool DBImpl::SchedulePendingFlush(const FlushRequest& flush_req) {
|
||||
bool DBImpl::EnqueuePendingFlush(const FlushRequest& flush_req) {
|
||||
mutex_.AssertHeld();
|
||||
bool enqueued = false;
|
||||
if (reject_new_background_jobs_) {
|
||||
@@ -3041,16 +3052,15 @@ bool DBImpl::SchedulePendingFlush(const FlushRequest& flush_req) {
|
||||
return enqueued;
|
||||
}
|
||||
|
||||
void DBImpl::SchedulePendingCompaction(ColumnFamilyData* cfd) {
|
||||
void DBImpl::EnqueuePendingCompaction(ColumnFamilyData* cfd) {
|
||||
mutex_.AssertHeld();
|
||||
if (reject_new_background_jobs_) {
|
||||
return;
|
||||
}
|
||||
if (!cfd->queued_for_compaction() && cfd->NeedsCompaction()) {
|
||||
TEST_SYNC_POINT_CALLBACK("SchedulePendingCompaction::cfd",
|
||||
TEST_SYNC_POINT_CALLBACK("EnqueuePendingCompaction::cfd",
|
||||
static_cast<void*>(cfd));
|
||||
AddToCompactionQueue(cfd);
|
||||
++unscheduled_compactions_;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3218,7 +3228,7 @@ Status DBImpl::BackgroundFlush(bool* made_progress, JobContext* job_context,
|
||||
#ifndef NDEBUG
|
||||
flush_req.reschedule_count += 1;
|
||||
#endif /* !NDEBUG */
|
||||
SchedulePendingFlush(flush_req);
|
||||
EnqueuePendingFlush(flush_req);
|
||||
*reason = flush_reason;
|
||||
*flush_rescheduled_to_retain_udt = true;
|
||||
return Status::TryAgain();
|
||||
@@ -3541,6 +3551,14 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
is_manual && manual_compaction->disallow_trivial_move;
|
||||
|
||||
CompactionJobStats compaction_job_stats;
|
||||
// Set is_remote_compaction to true on CompactionBegin Event if
|
||||
// compaction_service is set except for trivial moves. We do not know whether
|
||||
// remote compaction will actually be successfully scheduled, or fall back to
|
||||
// local at this time. CompactionCompleted event will tell the truth where
|
||||
// the compaction actually happened.
|
||||
compaction_job_stats.is_remote_compaction =
|
||||
immutable_db_options().compaction_service != nullptr;
|
||||
|
||||
Status status;
|
||||
if (!error_handler_.IsBGWorkStopped()) {
|
||||
if (shutting_down_.load(std::memory_order_acquire)) {
|
||||
@@ -3678,7 +3696,6 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
->ComputeCompactionScore(*(c->immutable_options()),
|
||||
*(c->mutable_cf_options()));
|
||||
AddToCompactionQueue(cfd);
|
||||
++unscheduled_compactions_;
|
||||
|
||||
c.reset();
|
||||
// Don't need to sleep here, because BackgroundCallCompaction
|
||||
@@ -3707,7 +3724,6 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
if (cfd->NeedsCompaction()) {
|
||||
// Yes, we need more compactions!
|
||||
AddToCompactionQueue(cfd);
|
||||
++unscheduled_compactions_;
|
||||
MaybeScheduleFlushOrCompaction();
|
||||
}
|
||||
}
|
||||
@@ -3768,6 +3784,8 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
ThreadStatusUtil::SetThreadOperation(ThreadStatus::OP_COMPACTION);
|
||||
|
||||
compaction_job_stats.num_input_files = c->num_input_files(0);
|
||||
// Trivial moves do not get compacted remotely
|
||||
compaction_job_stats.is_remote_compaction = false;
|
||||
|
||||
NotifyOnCompactionBegin(c->column_family_data(), c.get(), status,
|
||||
compaction_job_stats, job_context->job_id);
|
||||
@@ -3903,6 +3921,12 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
&bg_bottom_compaction_scheduled_);
|
||||
compaction_job.Prepare();
|
||||
|
||||
std::unique_ptr<std::list<uint64_t>::iterator> min_options_file_number_elem;
|
||||
if (immutable_db_options().compaction_service != nullptr) {
|
||||
min_options_file_number_elem.reset(
|
||||
new std::list<uint64_t>::iterator(CaptureOptionsFileNumber()));
|
||||
}
|
||||
|
||||
NotifyOnCompactionBegin(c->column_family_data(), c.get(), status,
|
||||
compaction_job_stats, job_context->job_id);
|
||||
mutex_.Unlock();
|
||||
@@ -3912,6 +3936,11 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
compaction_job.Run().PermitUncheckedError();
|
||||
TEST_SYNC_POINT("DBImpl::BackgroundCompaction:NonTrivial:AfterRun");
|
||||
mutex_.Lock();
|
||||
|
||||
if (immutable_db_options().compaction_service != nullptr) {
|
||||
ReleaseOptionsFileNumber(min_options_file_number_elem);
|
||||
}
|
||||
|
||||
status =
|
||||
compaction_job.Install(*c->mutable_cf_options(), &compaction_released);
|
||||
io_s = compaction_job.io_status();
|
||||
@@ -3997,7 +4026,6 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
*(c->mutable_cf_options()));
|
||||
if (!cfd->queued_for_compaction()) {
|
||||
AddToCompactionQueue(cfd);
|
||||
++unscheduled_compactions_;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4269,7 +4297,7 @@ void DBImpl::InstallSuperVersionAndScheduleWork(
|
||||
|
||||
// Whenever we install new SuperVersion, we might need to issue new flushes or
|
||||
// compactions.
|
||||
SchedulePendingCompaction(cfd);
|
||||
EnqueuePendingCompaction(cfd);
|
||||
MaybeScheduleFlushOrCompaction();
|
||||
|
||||
// Update max_total_in_memory_state_
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
#ifndef NDEBUG
|
||||
|
||||
#include "db/blob/blob_file_cache.h"
|
||||
#include "db/column_family.h"
|
||||
#include "db/db_impl/db_impl.h"
|
||||
#include "db/error_handler.h"
|
||||
@@ -199,6 +200,11 @@ Status DBImpl::TEST_GetBGError() {
|
||||
return error_handler_.GetBGError();
|
||||
}
|
||||
|
||||
bool DBImpl::TEST_IsRecoveryInProgress() {
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
return error_handler_.IsRecoveryInProgress();
|
||||
}
|
||||
|
||||
void DBImpl::TEST_LockMutex() { mutex_.Lock(); }
|
||||
|
||||
void DBImpl::TEST_UnlockMutex() { mutex_.Unlock(); }
|
||||
@@ -323,5 +329,49 @@ size_t DBImpl::TEST_EstimateInMemoryStatsHistorySize() const {
|
||||
InstrumentedMutexLock l(&const_cast<DBImpl*>(this)->stats_history_mutex_);
|
||||
return EstimateInMemoryStatsHistorySize();
|
||||
}
|
||||
|
||||
void DBImpl::TEST_VerifyNoObsoleteFilesCached(
|
||||
bool db_mutex_already_held) const {
|
||||
// This check is somewhat expensive and obscure to make a part of every
|
||||
// unit test in every build variety. Thus, we only enable it for ASAN builds.
|
||||
if (!kMustFreeHeapAllocations) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::optional<InstrumentedMutexLock> l;
|
||||
if (db_mutex_already_held) {
|
||||
mutex_.AssertHeld();
|
||||
} else {
|
||||
l.emplace(&mutex_);
|
||||
}
|
||||
|
||||
std::vector<uint64_t> live_files;
|
||||
for (auto cfd : *versions_->GetColumnFamilySet()) {
|
||||
if (cfd->IsDropped()) {
|
||||
continue;
|
||||
}
|
||||
// Sneakily add both SST and blob files to the same list
|
||||
cfd->current()->AddLiveFiles(&live_files, &live_files);
|
||||
}
|
||||
std::sort(live_files.begin(), live_files.end());
|
||||
|
||||
auto fn = [&live_files](const Slice& key, Cache::ObjectPtr, size_t,
|
||||
const Cache::CacheItemHelper* helper) {
|
||||
if (helper != BlobFileCache::GetHelper()) {
|
||||
// Skip non-blob files for now
|
||||
// FIXME: diagnose and fix the leaks of obsolete SST files revealed in
|
||||
// unit tests.
|
||||
return;
|
||||
}
|
||||
// See TableCache and BlobFileCache
|
||||
assert(key.size() == sizeof(uint64_t));
|
||||
uint64_t file_number;
|
||||
GetUnaligned(reinterpret_cast<const uint64_t*>(key.data()), &file_number);
|
||||
// Assert file is in sorted live_files
|
||||
assert(
|
||||
std::binary_search(live_files.begin(), live_files.end(), file_number));
|
||||
};
|
||||
table_cache_->ApplyToAllEntries(fn, {});
|
||||
}
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
#endif // NDEBUG
|
||||
@@ -47,7 +47,7 @@ Status DBImpl::SuggestCompactRange(ColumnFamilyHandle* column_family,
|
||||
// compaction score
|
||||
vstorage->ComputeCompactionScore(*cfd->ioptions(),
|
||||
*cfd->GetLatestMutableCFOptions());
|
||||
SchedulePendingCompaction(cfd);
|
||||
EnqueuePendingCompaction(cfd);
|
||||
MaybeScheduleFlushOrCompaction();
|
||||
}
|
||||
return Status::OK();
|
||||
|
||||
+82
-33
@@ -43,6 +43,14 @@ uint64_t DBImpl::GetObsoleteSstFilesSize() {
|
||||
return versions_->GetObsoleteSstFilesSize();
|
||||
}
|
||||
|
||||
uint64_t DBImpl::MinOptionsFileNumberToKeep() {
|
||||
mutex_.AssertHeld();
|
||||
if (!min_options_file_numbers_.empty()) {
|
||||
return *min_options_file_numbers_.begin();
|
||||
}
|
||||
return std::numeric_limits<uint64_t>::max();
|
||||
}
|
||||
|
||||
Status DBImpl::DisableFileDeletions() {
|
||||
Status s;
|
||||
int my_disable_delete_obsolete_files;
|
||||
@@ -147,6 +155,7 @@ void DBImpl::FindObsoleteFiles(JobContext* job_context, bool force,
|
||||
// here but later find newer generated unfinalized files while scanning.
|
||||
job_context->min_pending_output = MinObsoleteSstNumberToKeep();
|
||||
job_context->files_to_quarantine = error_handler_.GetFilesToQuarantine();
|
||||
job_context->min_options_file_number = MinOptionsFileNumberToKeep();
|
||||
|
||||
// Get obsolete files. This function will also update the list of
|
||||
// pending files in VersionSet().
|
||||
@@ -498,7 +507,7 @@ void DBImpl::PurgeObsoleteFiles(JobContext& state, bool schedule_only) {
|
||||
dbname_);
|
||||
|
||||
// File numbers of most recent two OPTIONS file in candidate_files (found in
|
||||
// previos FindObsoleteFiles(full_scan=true))
|
||||
// previous FindObsoleteFiles(full_scan=true))
|
||||
// At this point, there must not be any duplicate file numbers in
|
||||
// candidate_files.
|
||||
uint64_t optsfile_num1 = std::numeric_limits<uint64_t>::min();
|
||||
@@ -519,6 +528,11 @@ void DBImpl::PurgeObsoleteFiles(JobContext& state, bool schedule_only) {
|
||||
}
|
||||
}
|
||||
|
||||
// For remote compactions, we need to keep OPTIONS file that may get
|
||||
// referenced by the remote worker
|
||||
|
||||
optsfile_num2 = std::min(optsfile_num2, state.min_options_file_number);
|
||||
|
||||
// Close WALs before trying to delete them.
|
||||
for (const auto w : state.logs_to_free) {
|
||||
// TODO: maybe check the return value of Close.
|
||||
@@ -722,6 +736,38 @@ void DBImpl::DeleteObsoleteFiles() {
|
||||
mutex_.Lock();
|
||||
}
|
||||
|
||||
VersionEdit GetDBRecoveryEditForObsoletingMemTables(
|
||||
VersionSet* vset, const ColumnFamilyData& cfd,
|
||||
const autovector<VersionEdit*>& edit_list,
|
||||
const autovector<MemTable*>& memtables, LogsWithPrepTracker* prep_tracker) {
|
||||
VersionEdit wal_deletion_edit;
|
||||
uint64_t min_wal_number_to_keep = 0;
|
||||
assert(edit_list.size() > 0);
|
||||
if (vset->db_options()->allow_2pc) {
|
||||
// Note that if mempurge is successful, the edit_list will
|
||||
// not be applicable (contains info of new min_log number to keep,
|
||||
// and level 0 file path of SST file created during normal flush,
|
||||
// so both pieces of information are irrelevant after a successful
|
||||
// mempurge operation).
|
||||
min_wal_number_to_keep = PrecomputeMinLogNumberToKeep2PC(
|
||||
vset, cfd, edit_list, memtables, prep_tracker);
|
||||
|
||||
// We piggyback the information of earliest log file to keep in the
|
||||
// manifest entry for the last file flushed.
|
||||
} else {
|
||||
min_wal_number_to_keep =
|
||||
PrecomputeMinLogNumberToKeepNon2PC(vset, cfd, edit_list);
|
||||
}
|
||||
|
||||
wal_deletion_edit.SetMinLogNumberToKeep(min_wal_number_to_keep);
|
||||
if (vset->db_options()->track_and_verify_wals_in_manifest) {
|
||||
if (min_wal_number_to_keep > vset->GetWalSet().GetMinWalNumberToKeep()) {
|
||||
wal_deletion_edit.DeleteWalsBefore(min_wal_number_to_keep);
|
||||
}
|
||||
}
|
||||
return wal_deletion_edit;
|
||||
}
|
||||
|
||||
uint64_t FindMinPrepLogReferencedByMemTable(
|
||||
VersionSet* vset, const autovector<MemTable*>& memtables_to_flush) {
|
||||
uint64_t min_log = 0;
|
||||
@@ -921,57 +967,60 @@ uint64_t PrecomputeMinLogNumberToKeep2PC(
|
||||
}
|
||||
|
||||
void DBImpl::SetDBId(std::string&& id, bool read_only,
|
||||
RecoveryContext* recovery_ctx) {
|
||||
VersionEdit* version_edit) {
|
||||
assert(db_id_.empty());
|
||||
assert(!id.empty());
|
||||
db_id_ = std::move(id);
|
||||
if (!read_only && immutable_db_options_.write_dbid_to_manifest) {
|
||||
assert(recovery_ctx != nullptr);
|
||||
if (!read_only && version_edit) {
|
||||
assert(version_edit != nullptr);
|
||||
assert(versions_->GetColumnFamilySet() != nullptr);
|
||||
VersionEdit edit;
|
||||
edit.SetDBId(db_id_);
|
||||
version_edit->SetDBId(db_id_);
|
||||
versions_->db_id_ = db_id_;
|
||||
recovery_ctx->UpdateVersionEdits(
|
||||
versions_->GetColumnFamilySet()->GetDefault(), edit);
|
||||
}
|
||||
}
|
||||
|
||||
Status DBImpl::SetupDBId(const WriteOptions& write_options, bool read_only,
|
||||
RecoveryContext* recovery_ctx) {
|
||||
bool is_new_db, VersionEdit* version_edit) {
|
||||
Status s;
|
||||
// Check for the IDENTITY file and create it if not there or
|
||||
// broken or not matching manifest
|
||||
std::string db_id_in_file;
|
||||
s = fs_->FileExists(IdentityFileName(dbname_), IOOptions(), nullptr);
|
||||
if (s.ok()) {
|
||||
s = GetDbIdentityFromIdentityFile(&db_id_in_file);
|
||||
if (s.ok() && !db_id_in_file.empty()) {
|
||||
if (db_id_.empty()) {
|
||||
// Loaded from file and wasn't already known from manifest
|
||||
SetDBId(std::move(db_id_in_file), read_only, recovery_ctx);
|
||||
return s;
|
||||
} else if (db_id_ == db_id_in_file) {
|
||||
// Loaded from file and matches manifest
|
||||
return s;
|
||||
if (!is_new_db) {
|
||||
// Check for the IDENTITY file and create it if not there or
|
||||
// broken or not matching manifest
|
||||
std::string db_id_in_file;
|
||||
s = fs_->FileExists(IdentityFileName(dbname_), IOOptions(), nullptr);
|
||||
if (s.ok()) {
|
||||
s = GetDbIdentityFromIdentityFile(&db_id_in_file);
|
||||
if (s.ok() && !db_id_in_file.empty()) {
|
||||
if (db_id_.empty()) {
|
||||
// Loaded from file and wasn't already known from manifest
|
||||
SetDBId(std::move(db_id_in_file), read_only, version_edit);
|
||||
return s;
|
||||
} else if (db_id_ == db_id_in_file) {
|
||||
// Loaded from file and matches manifest
|
||||
return s;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (s.IsNotFound()) {
|
||||
s = Status::OK();
|
||||
}
|
||||
if (!s.ok()) {
|
||||
assert(s.IsIOError());
|
||||
return s;
|
||||
if (s.IsNotFound()) {
|
||||
s = Status::OK();
|
||||
}
|
||||
if (!s.ok()) {
|
||||
assert(s.IsIOError());
|
||||
return s;
|
||||
}
|
||||
}
|
||||
// Otherwise IDENTITY file is missing or no good.
|
||||
// Generate new id if needed
|
||||
if (db_id_.empty()) {
|
||||
SetDBId(env_->GenerateUniqueId(), read_only, recovery_ctx);
|
||||
SetDBId(env_->GenerateUniqueId(), read_only, version_edit);
|
||||
}
|
||||
// Persist it to IDENTITY file if allowed
|
||||
if (!read_only) {
|
||||
s = SetIdentityFile(write_options, env_, dbname_, db_id_);
|
||||
if (!read_only && immutable_db_options_.write_identity_file) {
|
||||
s = SetIdentityFile(write_options, env_, dbname_,
|
||||
immutable_db_options_.metadata_write_temperature,
|
||||
db_id_);
|
||||
}
|
||||
// NOTE: an obsolete IDENTITY file with write_identity_file=false is handled
|
||||
// elsewhere, so that it's only deleted after successful recovery
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
+47
-58
@@ -289,27 +289,25 @@ Status DBImpl::ValidateOptions(const DBOptions& db_options) {
|
||||
"start_time and end_time cannot be the same");
|
||||
}
|
||||
}
|
||||
|
||||
if (!db_options.write_dbid_to_manifest && !db_options.write_identity_file) {
|
||||
return Status::InvalidArgument(
|
||||
"write_dbid_to_manifest and write_identity_file cannot both be false");
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status DBImpl::NewDB(std::vector<std::string>* new_filenames) {
|
||||
VersionEdit new_db;
|
||||
VersionEdit new_db_edit;
|
||||
const WriteOptions write_options(Env::IOActivity::kDBOpen);
|
||||
Status s = SetIdentityFile(write_options, env_, dbname_);
|
||||
Status s = SetupDBId(write_options, /*read_only=*/false, /*is_new_db=*/true,
|
||||
&new_db_edit);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
if (immutable_db_options_.write_dbid_to_manifest) {
|
||||
std::string temp_db_id;
|
||||
s = GetDbIdentityFromIdentityFile(&temp_db_id);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
new_db.SetDBId(temp_db_id);
|
||||
}
|
||||
new_db.SetLogNumber(0);
|
||||
new_db.SetNextFile(2);
|
||||
new_db.SetLastSequence(0);
|
||||
new_db_edit.SetLogNumber(0);
|
||||
new_db_edit.SetNextFile(2);
|
||||
new_db_edit.SetLastSequence(0);
|
||||
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log, "Creating manifest 1 \n");
|
||||
const std::string manifest = DescriptorFileName(dbname_, 1);
|
||||
@@ -319,6 +317,12 @@ Status DBImpl::NewDB(std::vector<std::string>* new_filenames) {
|
||||
}
|
||||
std::unique_ptr<FSWritableFile> file;
|
||||
FileOptions file_options = fs_->OptimizeForManifestWrite(file_options_);
|
||||
// DB option takes precedence when not kUnknown
|
||||
if (immutable_db_options_.metadata_write_temperature !=
|
||||
Temperature::kUnknown) {
|
||||
file_options.temperature =
|
||||
immutable_db_options_.metadata_write_temperature;
|
||||
}
|
||||
s = NewWritableFile(fs_.get(), manifest, &file, file_options);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
@@ -335,7 +339,7 @@ Status DBImpl::NewDB(std::vector<std::string>* new_filenames) {
|
||||
tmp_set.Contains(FileType::kDescriptorFile)));
|
||||
log::Writer log(std::move(file_writer), 0, false);
|
||||
std::string record;
|
||||
new_db.EncodeTo(&record);
|
||||
new_db_edit.EncodeTo(&record);
|
||||
s = log.AddRecord(write_options, record);
|
||||
if (s.ok()) {
|
||||
s = SyncManifest(&immutable_db_options_, write_options, log.file());
|
||||
@@ -344,6 +348,7 @@ Status DBImpl::NewDB(std::vector<std::string>* new_filenames) {
|
||||
if (s.ok()) {
|
||||
// Make "CURRENT" file that points to the new manifest file.
|
||||
s = SetCurrentFile(write_options, fs_.get(), dbname_, 1,
|
||||
immutable_db_options_.metadata_write_temperature,
|
||||
directories_.GetDbDir());
|
||||
if (new_filenames) {
|
||||
new_filenames->emplace_back(
|
||||
@@ -520,7 +525,7 @@ Status DBImpl::Recover(
|
||||
}
|
||||
assert(s.ok());
|
||||
}
|
||||
assert(db_id_.empty());
|
||||
assert(is_new_db || db_id_.empty());
|
||||
Status s;
|
||||
bool missing_table_file = false;
|
||||
if (!immutable_db_options_.best_efforts_recovery) {
|
||||
@@ -666,7 +671,17 @@ Status DBImpl::Recover(
|
||||
}
|
||||
}
|
||||
}
|
||||
s = SetupDBId(write_options, read_only, recovery_ctx);
|
||||
if (is_new_db) {
|
||||
// Already set up DB ID in NewDB
|
||||
} else if (immutable_db_options_.write_dbid_to_manifest && recovery_ctx) {
|
||||
VersionEdit edit;
|
||||
s = SetupDBId(write_options, read_only, is_new_db, &edit);
|
||||
recovery_ctx->UpdateVersionEdits(
|
||||
versions_->GetColumnFamilySet()->GetDefault(), edit);
|
||||
} else {
|
||||
s = SetupDBId(write_options, read_only, is_new_db, nullptr);
|
||||
}
|
||||
assert(!s.ok() || !db_id_.empty());
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log, "DB ID: %s\n", db_id_.c_str());
|
||||
if (s.ok() && !read_only) {
|
||||
s = MaybeUpdateNextFileNumber(recovery_ctx);
|
||||
@@ -1654,7 +1669,8 @@ Status DBImpl::WriteLevel0TableForRecovery(int job_id, ColumnFamilyData* cfd,
|
||||
TableProperties table_properties;
|
||||
{
|
||||
ScopedArenaPtr<InternalIterator> iter(
|
||||
mem->NewIterator(ro, /*seqno_to_time_mapping=*/nullptr, &arena));
|
||||
mem->NewIterator(ro, /*seqno_to_time_mapping=*/nullptr, &arena,
|
||||
/*prefix_extractor=*/nullptr));
|
||||
ROCKS_LOG_DEBUG(immutable_db_options_.info_log,
|
||||
"[%s] [WriteLevel0TableForRecovery]"
|
||||
" Level-0 table #%" PRIu64 ": started",
|
||||
@@ -1673,7 +1689,8 @@ Status DBImpl::WriteLevel0TableForRecovery(int job_id, ColumnFamilyData* cfd,
|
||||
meta.oldest_ancester_time = current_time;
|
||||
meta.epoch_number = cfd->NewEpochNumber();
|
||||
{
|
||||
auto write_hint = cfd->CalculateSSTWriteHint(0);
|
||||
auto write_hint =
|
||||
cfd->current()->storage_info()->CalculateSSTWriteHint(/*level=*/0);
|
||||
mutex_.Unlock();
|
||||
|
||||
SequenceNumber earliest_write_conflict_snapshot;
|
||||
@@ -1936,6 +1953,10 @@ IOStatus DBImpl::CreateWAL(const WriteOptions& write_options,
|
||||
BuildDBOptions(immutable_db_options_, mutable_db_options_);
|
||||
FileOptions opt_file_options =
|
||||
fs_->OptimizeForLogWrite(file_options_, db_options);
|
||||
// DB option takes precedence when not kUnknown
|
||||
if (immutable_db_options_.wal_write_temperature != Temperature::kUnknown) {
|
||||
opt_file_options.temperature = immutable_db_options_.wal_write_temperature;
|
||||
}
|
||||
std::string wal_dir = immutable_db_options_.GetWalDir();
|
||||
std::string log_fname = LogFileName(wal_dir, log_file_num);
|
||||
|
||||
@@ -1975,46 +1996,7 @@ IOStatus DBImpl::CreateWAL(const WriteOptions& write_options,
|
||||
|
||||
void DBImpl::TrackExistingDataFiles(
|
||||
const std::vector<std::string>& existing_data_files) {
|
||||
auto sfm = static_cast<SstFileManagerImpl*>(
|
||||
immutable_db_options_.sst_file_manager.get());
|
||||
assert(sfm);
|
||||
std::vector<ColumnFamilyMetaData> metadata;
|
||||
GetAllColumnFamilyMetaData(&metadata);
|
||||
|
||||
std::unordered_set<std::string> referenced_files;
|
||||
for (const auto& md : metadata) {
|
||||
for (const auto& lmd : md.levels) {
|
||||
for (const auto& fmd : lmd.files) {
|
||||
// We're assuming that each sst file name exists in at most one of
|
||||
// the paths.
|
||||
std::string file_path =
|
||||
fmd.directory + kFilePathSeparator + fmd.relative_filename;
|
||||
sfm->OnAddFile(file_path, fmd.size).PermitUncheckedError();
|
||||
referenced_files.insert(file_path);
|
||||
}
|
||||
}
|
||||
for (const auto& bmd : md.blob_files) {
|
||||
std::string name = bmd.blob_file_name;
|
||||
// The BlobMetaData.blob_file_name may start with "/".
|
||||
if (!name.empty() && name[0] == kFilePathSeparator) {
|
||||
name = name.substr(1);
|
||||
}
|
||||
// We're assuming that each blob file name exists in at most one of
|
||||
// the paths.
|
||||
std::string file_path = bmd.blob_file_path + kFilePathSeparator + name;
|
||||
sfm->OnAddFile(file_path, bmd.blob_file_size).PermitUncheckedError();
|
||||
referenced_files.insert(file_path);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& file_path : existing_data_files) {
|
||||
if (referenced_files.find(file_path) != referenced_files.end()) {
|
||||
continue;
|
||||
}
|
||||
// There shouldn't be any duplicated files. In case there is, SstFileManager
|
||||
// will take care of deduping it.
|
||||
sfm->OnAddFile(file_path).PermitUncheckedError();
|
||||
}
|
||||
TrackOrUntrackFiles(existing_data_files, /*track=*/true);
|
||||
}
|
||||
|
||||
Status DBImpl::Open(const DBOptions& db_options, const std::string& dbname,
|
||||
@@ -2158,6 +2140,13 @@ Status DBImpl::Open(const DBOptions& db_options, const std::string& dbname,
|
||||
s = impl->LogAndApplyForRecovery(recovery_ctx);
|
||||
}
|
||||
|
||||
if (s.ok() && !impl->immutable_db_options_.write_identity_file) {
|
||||
// On successful recovery, delete an obsolete IDENTITY file to avoid DB ID
|
||||
// inconsistency
|
||||
impl->env_->DeleteFile(IdentityFileName(impl->dbname_))
|
||||
.PermitUncheckedError();
|
||||
}
|
||||
|
||||
if (s.ok() && impl->immutable_db_options_.persist_stats_to_disk) {
|
||||
impl->mutex_.AssertHeld();
|
||||
s = impl->InitPersistStatsColumnFamily();
|
||||
|
||||
@@ -12,7 +12,8 @@
|
||||
#include "logging/auto_roll_logger.h"
|
||||
#include "logging/logging.h"
|
||||
#include "monitoring/perf_context_imp.h"
|
||||
#include "rocksdb/configurable.h"
|
||||
#include "rocksdb/convenience.h"
|
||||
#include "rocksdb/utilities/options_util.h"
|
||||
#include "util/cast_util.h"
|
||||
#include "util/write_batch_util.h"
|
||||
|
||||
@@ -938,69 +939,101 @@ Status DB::OpenAndCompact(
|
||||
const std::string& output_directory, const std::string& input,
|
||||
std::string* output,
|
||||
const CompactionServiceOptionsOverride& override_options) {
|
||||
// Check for cancellation
|
||||
if (options.canceled && options.canceled->load(std::memory_order_acquire)) {
|
||||
return Status::Incomplete(Status::SubCode::kManualCompactionPaused);
|
||||
}
|
||||
|
||||
// 1. Deserialize Compaction Input
|
||||
CompactionServiceInput compaction_input;
|
||||
Status s = CompactionServiceInput::Read(input, &compaction_input);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
compaction_input.db_options.max_open_files = -1;
|
||||
compaction_input.db_options.compaction_service = nullptr;
|
||||
if (compaction_input.db_options.statistics) {
|
||||
compaction_input.db_options.statistics.reset();
|
||||
}
|
||||
compaction_input.db_options.env = override_options.env;
|
||||
compaction_input.db_options.file_checksum_gen_factory =
|
||||
override_options.file_checksum_gen_factory;
|
||||
compaction_input.db_options.statistics = override_options.statistics;
|
||||
compaction_input.column_family.options.comparator =
|
||||
override_options.comparator;
|
||||
compaction_input.column_family.options.merge_operator =
|
||||
override_options.merge_operator;
|
||||
compaction_input.column_family.options.compaction_filter =
|
||||
override_options.compaction_filter;
|
||||
compaction_input.column_family.options.compaction_filter_factory =
|
||||
override_options.compaction_filter_factory;
|
||||
compaction_input.column_family.options.prefix_extractor =
|
||||
override_options.prefix_extractor;
|
||||
compaction_input.column_family.options.table_factory =
|
||||
override_options.table_factory;
|
||||
compaction_input.column_family.options.sst_partitioner_factory =
|
||||
override_options.sst_partitioner_factory;
|
||||
compaction_input.column_family.options.table_properties_collector_factories =
|
||||
override_options.table_properties_collector_factories;
|
||||
compaction_input.db_options.listeners = override_options.listeners;
|
||||
// 2. Load the options
|
||||
DBOptions db_options;
|
||||
ConfigOptions config_options;
|
||||
config_options.env = override_options.env;
|
||||
std::vector<ColumnFamilyDescriptor> all_column_families;
|
||||
|
||||
std::vector<ColumnFamilyDescriptor> column_families;
|
||||
column_families.push_back(compaction_input.column_family);
|
||||
// TODO: we have to open default CF, because of an implementation limitation,
|
||||
// currently we just use the same CF option from input, which is not collect
|
||||
// and open may fail.
|
||||
if (compaction_input.column_family.name != kDefaultColumnFamilyName) {
|
||||
column_families.emplace_back(kDefaultColumnFamilyName,
|
||||
compaction_input.column_family.options);
|
||||
}
|
||||
std::string options_file_name =
|
||||
OptionsFileName(name, compaction_input.options_file_number);
|
||||
|
||||
DB* db;
|
||||
std::vector<ColumnFamilyHandle*> handles;
|
||||
|
||||
s = DB::OpenAsSecondary(compaction_input.db_options, name, output_directory,
|
||||
column_families, &handles, &db);
|
||||
s = LoadOptionsFromFile(config_options, options_file_name, &db_options,
|
||||
&all_column_families);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
// 3. Override pointer configurations in DBOptions with
|
||||
// CompactionServiceOptionsOverride
|
||||
db_options.env = override_options.env;
|
||||
db_options.file_checksum_gen_factory =
|
||||
override_options.file_checksum_gen_factory;
|
||||
db_options.statistics = override_options.statistics;
|
||||
db_options.listeners = override_options.listeners;
|
||||
db_options.compaction_service = nullptr;
|
||||
// We will close the DB after the compaction anyway.
|
||||
// Open as many files as needed for the compaction.
|
||||
db_options.max_open_files = -1;
|
||||
|
||||
// 4. Filter CFs that are needed for OpenAndCompact()
|
||||
// We do not need to open all column families for the remote compaction.
|
||||
// Only open default CF + target CF. If target CF == default CF, we will open
|
||||
// just the default CF (Due to current limitation, DB cannot open without the
|
||||
// default CF)
|
||||
std::vector<ColumnFamilyDescriptor> column_families;
|
||||
for (auto& cf : all_column_families) {
|
||||
if (cf.name == compaction_input.cf_name) {
|
||||
cf.options.comparator = override_options.comparator;
|
||||
cf.options.merge_operator = override_options.merge_operator;
|
||||
cf.options.compaction_filter = override_options.compaction_filter;
|
||||
cf.options.compaction_filter_factory =
|
||||
override_options.compaction_filter_factory;
|
||||
cf.options.prefix_extractor = override_options.prefix_extractor;
|
||||
cf.options.table_factory = override_options.table_factory;
|
||||
cf.options.sst_partitioner_factory =
|
||||
override_options.sst_partitioner_factory;
|
||||
cf.options.table_properties_collector_factories =
|
||||
override_options.table_properties_collector_factories;
|
||||
column_families.emplace_back(cf);
|
||||
} else if (cf.name == kDefaultColumnFamilyName) {
|
||||
column_families.emplace_back(cf);
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Open db As Secondary
|
||||
DB* db;
|
||||
std::vector<ColumnFamilyHandle*> handles;
|
||||
s = DB::OpenAsSecondary(db_options, name, output_directory, column_families,
|
||||
&handles, &db);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
assert(db);
|
||||
|
||||
// 6. Find the handle of the Column Family that this will compact
|
||||
ColumnFamilyHandle* cfh = nullptr;
|
||||
for (auto* handle : handles) {
|
||||
if (compaction_input.cf_name == handle->GetName()) {
|
||||
cfh = handle;
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert(cfh);
|
||||
|
||||
// 7. Run the compaction without installation.
|
||||
// Output will be stored in the directory specified by output_directory
|
||||
CompactionServiceResult compaction_result;
|
||||
DBImplSecondary* db_secondary = static_cast_with_check<DBImplSecondary>(db);
|
||||
assert(handles.size() > 0);
|
||||
s = db_secondary->CompactWithoutInstallation(
|
||||
options, handles[0], compaction_input, &compaction_result);
|
||||
s = db_secondary->CompactWithoutInstallation(options, cfh, compaction_input,
|
||||
&compaction_result);
|
||||
|
||||
// 8. Serialize the result
|
||||
Status serialization_status = compaction_result.Write(output);
|
||||
|
||||
// 9. Close the db and return
|
||||
for (auto& handle : handles) {
|
||||
delete handle;
|
||||
}
|
||||
|
||||
+14
-12
@@ -656,7 +656,7 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
|
||||
if (!io_s.ok()) {
|
||||
// Check WriteToWAL status
|
||||
IOStatusCheck(io_s);
|
||||
WALIOStatusCheck(io_s);
|
||||
}
|
||||
if (!w.CallbackFailed()) {
|
||||
if (!io_s.ok()) {
|
||||
@@ -799,7 +799,7 @@ Status DBImpl::PipelinedWriteImpl(const WriteOptions& write_options,
|
||||
|
||||
if (!io_s.ok()) {
|
||||
// Check WriteToWAL status
|
||||
IOStatusCheck(io_s);
|
||||
WALIOStatusCheck(io_s);
|
||||
} else if (!w.CallbackFailed()) {
|
||||
WriteStatusCheck(w.status);
|
||||
}
|
||||
@@ -1077,7 +1077,7 @@ Status DBImpl::WriteImplWALOnly(
|
||||
// This error checking and return is moved up to avoid using uninitialized
|
||||
// last_sequence.
|
||||
if (!io_s.ok()) {
|
||||
IOStatusCheck(io_s);
|
||||
WALIOStatusCheck(io_s);
|
||||
write_thread->ExitAsBatchGroupLeader(write_group, status);
|
||||
return status;
|
||||
}
|
||||
@@ -1175,7 +1175,7 @@ void DBImpl::WriteStatusCheck(const Status& status) {
|
||||
}
|
||||
}
|
||||
|
||||
void DBImpl::IOStatusCheck(const IOStatus& io_status) {
|
||||
void DBImpl::WALIOStatusCheck(const IOStatus& io_status) {
|
||||
// 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() &&
|
||||
@@ -1183,7 +1183,8 @@ void DBImpl::IOStatusCheck(const IOStatus& io_status) {
|
||||
io_status.IsIOFenced()) {
|
||||
mutex_.Lock();
|
||||
// Maybe change the return status to void?
|
||||
error_handler_.SetBGError(io_status, BackgroundErrorReason::kWriteCallback);
|
||||
error_handler_.SetBGError(io_status, BackgroundErrorReason::kWriteCallback,
|
||||
/*wal_related=*/true);
|
||||
mutex_.Unlock();
|
||||
} else {
|
||||
// Force writable file to be continue writable.
|
||||
@@ -1789,13 +1790,13 @@ Status DBImpl::SwitchWAL(WriteContext* write_context) {
|
||||
if (!immutable_db_options_.atomic_flush) {
|
||||
FlushRequest flush_req;
|
||||
GenerateFlushRequest({cfd}, FlushReason::kWalFull, &flush_req);
|
||||
SchedulePendingFlush(flush_req);
|
||||
EnqueuePendingFlush(flush_req);
|
||||
}
|
||||
}
|
||||
if (immutable_db_options_.atomic_flush) {
|
||||
FlushRequest flush_req;
|
||||
GenerateFlushRequest(cfds, FlushReason::kWalFull, &flush_req);
|
||||
SchedulePendingFlush(flush_req);
|
||||
EnqueuePendingFlush(flush_req);
|
||||
}
|
||||
MaybeScheduleFlushOrCompaction();
|
||||
}
|
||||
@@ -1881,13 +1882,13 @@ Status DBImpl::HandleWriteBufferManagerFlush(WriteContext* write_context) {
|
||||
FlushRequest flush_req;
|
||||
GenerateFlushRequest({cfd}, FlushReason::kWriteBufferManager,
|
||||
&flush_req);
|
||||
SchedulePendingFlush(flush_req);
|
||||
EnqueuePendingFlush(flush_req);
|
||||
}
|
||||
}
|
||||
if (immutable_db_options_.atomic_flush) {
|
||||
FlushRequest flush_req;
|
||||
GenerateFlushRequest(cfds, FlushReason::kWriteBufferManager, &flush_req);
|
||||
SchedulePendingFlush(flush_req);
|
||||
EnqueuePendingFlush(flush_req);
|
||||
}
|
||||
MaybeScheduleFlushOrCompaction();
|
||||
}
|
||||
@@ -2163,12 +2164,12 @@ Status DBImpl::ScheduleFlushes(WriteContext* context) {
|
||||
AssignAtomicFlushSeq(cfds);
|
||||
FlushRequest flush_req;
|
||||
GenerateFlushRequest(cfds, FlushReason::kWriteBufferFull, &flush_req);
|
||||
SchedulePendingFlush(flush_req);
|
||||
EnqueuePendingFlush(flush_req);
|
||||
} else {
|
||||
for (auto* cfd : cfds) {
|
||||
FlushRequest flush_req;
|
||||
GenerateFlushRequest({cfd}, FlushReason::kWriteBufferFull, &flush_req);
|
||||
SchedulePendingFlush(flush_req);
|
||||
EnqueuePendingFlush(flush_req);
|
||||
}
|
||||
}
|
||||
MaybeScheduleFlushOrCompaction();
|
||||
@@ -2326,7 +2327,8 @@ Status DBImpl::SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context) {
|
||||
// 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
|
||||
if (!io_s.ok()) {
|
||||
error_handler_.SetBGError(io_s, BackgroundErrorReason::kMemTable);
|
||||
error_handler_.SetBGError(io_s, BackgroundErrorReason::kMemTable,
|
||||
/*wal_related=*/true);
|
||||
} else {
|
||||
error_handler_.SetBGError(s, BackgroundErrorReason::kMemTable);
|
||||
}
|
||||
|
||||
@@ -895,6 +895,81 @@ TEST_P(DBIOCorruptionTest, ManifestCorruptionRetry) {
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
TEST_P(DBIOCorruptionTest, FooterReadCorruptionRetry) {
|
||||
Random rnd(300);
|
||||
bool retry = false;
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"ReadFooterFromFileInternal:0", [&](void* arg) {
|
||||
Slice* data = static_cast<Slice*>(arg);
|
||||
if (!retry) {
|
||||
std::memcpy(const_cast<char*>(data->data()),
|
||||
rnd.RandomString(static_cast<int>(data->size())).c_str(),
|
||||
data->size());
|
||||
retry = true;
|
||||
}
|
||||
});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
ASSERT_OK(Put("key1", "val1"));
|
||||
Status s = Flush();
|
||||
if (std::get<2>(GetParam())) {
|
||||
ASSERT_OK(s);
|
||||
ASSERT_EQ(stats()->getTickerCount(FILE_READ_CORRUPTION_RETRY_COUNT), 1);
|
||||
ASSERT_EQ(stats()->getTickerCount(FILE_READ_CORRUPTION_RETRY_SUCCESS_COUNT),
|
||||
1);
|
||||
|
||||
std::string val;
|
||||
ReadOptions ro;
|
||||
ro.async_io = std::get<1>(GetParam());
|
||||
ASSERT_OK(dbfull()->Get(ro, "key1", &val));
|
||||
ASSERT_EQ(val, "val1");
|
||||
} else {
|
||||
ASSERT_NOK(s);
|
||||
ASSERT_EQ(stats()->getTickerCount(FILE_READ_CORRUPTION_RETRY_COUNT), 0);
|
||||
ASSERT_GT(stats()->getTickerCount(SST_FOOTER_CORRUPTION_COUNT), 0);
|
||||
}
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
|
||||
TEST_P(DBIOCorruptionTest, TablePropertiesCorruptionRetry) {
|
||||
Random rnd(300);
|
||||
bool retry = false;
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"ReadTablePropertiesHelper:0", [&](void* arg) {
|
||||
Slice* data = static_cast<Slice*>(arg);
|
||||
if (!retry) {
|
||||
std::memcpy(const_cast<char*>(data->data()),
|
||||
rnd.RandomString(static_cast<int>(data->size())).c_str(),
|
||||
data->size());
|
||||
retry = true;
|
||||
}
|
||||
});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
ASSERT_OK(Put("key1", "val1"));
|
||||
Status s = Flush();
|
||||
if (std::get<2>(GetParam())) {
|
||||
ASSERT_OK(s);
|
||||
ASSERT_EQ(stats()->getTickerCount(FILE_READ_CORRUPTION_RETRY_COUNT), 1);
|
||||
ASSERT_EQ(stats()->getTickerCount(FILE_READ_CORRUPTION_RETRY_SUCCESS_COUNT),
|
||||
1);
|
||||
|
||||
std::string val;
|
||||
ReadOptions ro;
|
||||
ro.async_io = std::get<1>(GetParam());
|
||||
ASSERT_OK(dbfull()->Get(ro, "key1", &val));
|
||||
ASSERT_EQ(val, "val1");
|
||||
} else {
|
||||
ASSERT_NOK(s);
|
||||
ASSERT_EQ(stats()->getTickerCount(FILE_READ_CORRUPTION_RETRY_COUNT), 0);
|
||||
}
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
|
||||
// The parameters are - 1. Use FS provided buffer, 2. Use async IO ReadOption,
|
||||
// 3. Retry with verify_and_reconstruct_read IOOption
|
||||
INSTANTIATE_TEST_CASE_P(DBIOCorruptionTest, DBIOCorruptionTest,
|
||||
|
||||
+7
-3
@@ -65,9 +65,8 @@ DBIter::DBIter(Env* _env, const ReadOptions& read_options,
|
||||
valid_(false),
|
||||
current_entry_is_merged_(false),
|
||||
is_key_seqnum_zero_(false),
|
||||
prefix_same_as_start_(mutable_cf_options.prefix_extractor
|
||||
? read_options.prefix_same_as_start
|
||||
: false),
|
||||
prefix_same_as_start_(
|
||||
prefix_extractor_ ? read_options.prefix_same_as_start : false),
|
||||
pin_thru_lifetime_(read_options.pin_data),
|
||||
expect_total_order_inner_iter_(prefix_extractor_ == nullptr ||
|
||||
read_options.total_order_seek ||
|
||||
@@ -93,6 +92,9 @@ DBIter::DBIter(Env* _env, const ReadOptions& read_options,
|
||||
status_.PermitUncheckedError();
|
||||
assert(timestamp_size_ ==
|
||||
user_comparator_.user_comparator()->timestamp_size());
|
||||
// prefix_seek_opt_in_only should force total_order_seek whereever the caller
|
||||
// is duplicating the original ReadOptions
|
||||
assert(!ioptions.prefix_seek_opt_in_only || read_options.total_order_seek);
|
||||
}
|
||||
|
||||
Status DBIter::GetProperty(std::string prop_name, std::string* prop) {
|
||||
@@ -538,6 +540,8 @@ bool DBIter::FindNextUserEntryInternal(bool skipping_saved_key,
|
||||
} else {
|
||||
iter_.Next();
|
||||
}
|
||||
// This could be a long-running operation due to tombstones, etc.
|
||||
ROCKSDB_THREAD_YIELD_HOOK();
|
||||
} while (iter_.Valid());
|
||||
|
||||
valid_ = false;
|
||||
|
||||
@@ -244,7 +244,7 @@ TEST_F(DBSecondaryTest, SimpleInternalCompaction) {
|
||||
ASSERT_EQ(largest.user_key().ToString(), "foo");
|
||||
ASSERT_EQ(result.output_level, 1);
|
||||
ASSERT_EQ(result.output_path, this->secondary_path_);
|
||||
ASSERT_EQ(result.num_output_records, 2);
|
||||
ASSERT_EQ(result.stats.num_output_records, 2);
|
||||
ASSERT_GT(result.bytes_written, 0);
|
||||
ASSERT_OK(result.status);
|
||||
}
|
||||
|
||||
@@ -383,12 +383,16 @@ TEST_F(DBSSTTest, DBWithSstFileManager) {
|
||||
ASSERT_EQ(files_moved, 0);
|
||||
|
||||
Close();
|
||||
ASSERT_EQ(sfm->GetTrackedFiles().size(), 0) << "sfm should be empty";
|
||||
ASSERT_EQ(sfm->GetTotalSize(), 0) << "sfm should be empty";
|
||||
Reopen(options);
|
||||
ASSERT_EQ(sfm->GetTrackedFiles(), files_in_db);
|
||||
ASSERT_EQ(sfm->GetTotalSize(), total_files_size);
|
||||
|
||||
// Verify that we track all the files again after the DB is closed and opened
|
||||
Close();
|
||||
ASSERT_EQ(sfm->GetTrackedFiles().size(), 0) << "sfm should be empty";
|
||||
ASSERT_EQ(sfm->GetTotalSize(), 0) << "sfm should be empty";
|
||||
sst_file_manager.reset(NewSstFileManager(env_));
|
||||
options.sst_file_manager = sst_file_manager;
|
||||
sfm = static_cast<SstFileManagerImpl*>(sst_file_manager.get());
|
||||
@@ -439,6 +443,11 @@ TEST_F(DBSSTTest, DBWithSstFileManagerForBlobFiles) {
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"SstFileManagerImpl::OnMoveFile", [&](void* /*arg*/) { files_moved++; });
|
||||
|
||||
int64_t untracked_files = 0;
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"SstFileManagerImpl::OnUntrackFile",
|
||||
[&](void* /*arg*/) { ++untracked_files; });
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
Options options = CurrentOptions();
|
||||
@@ -485,6 +494,10 @@ TEST_F(DBSSTTest, DBWithSstFileManagerForBlobFiles) {
|
||||
}
|
||||
ASSERT_EQ(sfm->GetTotalSize(), total_files_size);
|
||||
Close();
|
||||
ASSERT_EQ(untracked_files, files_in_db.size());
|
||||
untracked_files = 0;
|
||||
ASSERT_EQ(sfm->GetTrackedFiles().size(), 0) << "sfm should be empty";
|
||||
ASSERT_EQ(sfm->GetTotalSize(), 0) << "sfm should be empty";
|
||||
|
||||
Reopen(options);
|
||||
ASSERT_EQ(sfm->GetTrackedFiles(), files_in_db);
|
||||
@@ -492,6 +505,10 @@ TEST_F(DBSSTTest, DBWithSstFileManagerForBlobFiles) {
|
||||
|
||||
// Verify that we track all the files again after the DB is closed and opened.
|
||||
Close();
|
||||
ASSERT_EQ(untracked_files, files_in_db.size());
|
||||
untracked_files = 0;
|
||||
ASSERT_EQ(sfm->GetTrackedFiles().size(), 0) << "sfm should be empty";
|
||||
ASSERT_EQ(sfm->GetTotalSize(), 0) << "sfm should be empty";
|
||||
|
||||
sst_file_manager.reset(NewSstFileManager(env_));
|
||||
options.sst_file_manager = sst_file_manager;
|
||||
@@ -507,6 +524,10 @@ TEST_F(DBSSTTest, DBWithSstFileManagerForBlobFiles) {
|
||||
ASSERT_EQ(files_deleted, 0);
|
||||
ASSERT_EQ(files_scheduled_to_delete, 0);
|
||||
Close();
|
||||
ASSERT_EQ(untracked_files, files_in_db.size());
|
||||
untracked_files = 0;
|
||||
ASSERT_EQ(sfm->GetTrackedFiles().size(), 0) << "sfm should be empty";
|
||||
ASSERT_EQ(sfm->GetTotalSize(), 0) << "sfm should be empty";
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"SstFileManagerImpl::ScheduleUnaccountedFileDeletion", [&](void* arg) {
|
||||
assert(arg);
|
||||
@@ -666,6 +687,9 @@ TEST_F(DBSSTTest, DBWithSstFileManagerForBlobFilesWithGC) {
|
||||
}
|
||||
|
||||
Close();
|
||||
ASSERT_EQ(sfm->GetTrackedFiles().size(), 0) << "sfm should be empty";
|
||||
ASSERT_EQ(sfm->GetTotalSize(), 0) << "sfm should be empty";
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"SstFileManagerImpl::ScheduleUnaccountedFileDeletion", [&](void* arg) {
|
||||
assert(arg);
|
||||
|
||||
+267
-22
@@ -10,6 +10,7 @@
|
||||
#include <atomic>
|
||||
#include <cstdlib>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
|
||||
#include "db/db_test_util.h"
|
||||
@@ -26,6 +27,7 @@
|
||||
#include "rocksdb/utilities/replayer.h"
|
||||
#include "rocksdb/wal_filter.h"
|
||||
#include "test_util/testutil.h"
|
||||
#include "util/defer.h"
|
||||
#include "util/random.h"
|
||||
#include "utilities/fault_injection_env.h"
|
||||
|
||||
@@ -5595,32 +5597,45 @@ TEST_F(DBTest2, PrefixBloomFilteredOut) {
|
||||
bbto.filter_policy.reset(NewBloomFilterPolicy(10, false));
|
||||
bbto.whole_key_filtering = false;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(bbto));
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// Construct two L1 files with keys:
|
||||
// f1:[aaa1 ccc1] f2:[ddd0]
|
||||
ASSERT_OK(Put("aaa1", ""));
|
||||
ASSERT_OK(Put("ccc1", ""));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Put("ddd0", ""));
|
||||
ASSERT_OK(Flush());
|
||||
CompactRangeOptions cro;
|
||||
cro.bottommost_level_compaction = BottommostLevelCompaction::kSkip;
|
||||
ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
|
||||
// This test is also the primary test for prefix_seek_opt_in_only
|
||||
for (bool opt_in : {false, true}) {
|
||||
options.prefix_seek_opt_in_only = opt_in;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
Iterator* iter = db_->NewIterator(ReadOptions());
|
||||
ASSERT_OK(iter->status());
|
||||
// Construct two L1 files with keys:
|
||||
// f1:[aaa1 ccc1] f2:[ddd0]
|
||||
ASSERT_OK(Put("aaa1", ""));
|
||||
ASSERT_OK(Put("ccc1", ""));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Put("ddd0", ""));
|
||||
ASSERT_OK(Flush());
|
||||
CompactRangeOptions cro;
|
||||
cro.bottommost_level_compaction = BottommostLevelCompaction::kSkip;
|
||||
ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
|
||||
|
||||
// Bloom filter is filterd out by f1.
|
||||
// This is just one of several valid position following the contract.
|
||||
// Postioning to ccc1 or ddd0 is also valid. This is just to validate
|
||||
// the behavior of the current implementation. If underlying implementation
|
||||
// changes, the test might fail here.
|
||||
iter->Seek("bbb1");
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_FALSE(iter->Valid());
|
||||
ReadOptions ropts;
|
||||
for (bool same : {false, true}) {
|
||||
ropts.prefix_same_as_start = same;
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(ropts));
|
||||
ASSERT_OK(iter->status());
|
||||
|
||||
delete iter;
|
||||
iter->Seek("bbb1");
|
||||
ASSERT_OK(iter->status());
|
||||
if (opt_in && !same) {
|
||||
// Unbounded total order seek
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ(iter->key(), "ccc1");
|
||||
} else {
|
||||
// Bloom filter is filterd out by f1. When same == false, this is just
|
||||
// one valid position following the contract. Postioning to ccc1 or ddd0
|
||||
// is also valid. This is just to validate the behavior of the current
|
||||
// implementation. If underlying implementation changes, the test might
|
||||
// fail here.
|
||||
ASSERT_FALSE(iter->Valid());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBTest2, RowCacheSnapshot) {
|
||||
@@ -5985,6 +6000,7 @@ TEST_F(DBTest2, ChangePrefixExtractor) {
|
||||
// create a DB with block prefix index
|
||||
BlockBasedTableOptions table_options;
|
||||
Options options = CurrentOptions();
|
||||
options.prefix_seek_opt_in_only = false; // Use legacy prefix seek
|
||||
|
||||
// Sometimes filter is checked based on upper bound. Assert counters
|
||||
// for that case. Otherwise, only check data correctness.
|
||||
@@ -6544,6 +6560,235 @@ TEST_P(RenameCurrentTest, Compaction) {
|
||||
ASSERT_EQ("d_value", Get("d"));
|
||||
}
|
||||
|
||||
TEST_F(DBTest2, VariousFileTemperatures) {
|
||||
constexpr size_t kNumberFileTypes = static_cast<size_t>(kBlobFile) + 1U;
|
||||
|
||||
struct MyTestFS : public FileTemperatureTestFS {
|
||||
explicit MyTestFS(const std::shared_ptr<FileSystem>& fs)
|
||||
: FileTemperatureTestFS(fs) {
|
||||
Reset();
|
||||
}
|
||||
|
||||
IOStatus NewWritableFile(const std::string& fname, const FileOptions& opts,
|
||||
std::unique_ptr<FSWritableFile>* result,
|
||||
IODebugContext* dbg) override {
|
||||
IOStatus ios =
|
||||
FileTemperatureTestFS::NewWritableFile(fname, opts, result, dbg);
|
||||
if (ios.ok()) {
|
||||
uint64_t number;
|
||||
FileType type;
|
||||
if (ParseFileName(GetFileName(fname), &number, "LOG", &type)) {
|
||||
if (type == kTableFile) {
|
||||
// Not checked here
|
||||
} else if (type == kWalFile) {
|
||||
if (opts.temperature != expected_wal_temperature) {
|
||||
std::cerr << "Attempt to open " << fname << " with temperature "
|
||||
<< temperature_to_string[opts.temperature]
|
||||
<< " rather than "
|
||||
<< temperature_to_string[expected_wal_temperature]
|
||||
<< std::endl;
|
||||
assert(false);
|
||||
}
|
||||
} else if (type == kDescriptorFile) {
|
||||
if (opts.temperature != expected_manifest_temperature) {
|
||||
std::cerr << "Attempt to open " << fname << " with temperature "
|
||||
<< temperature_to_string[opts.temperature]
|
||||
<< " rather than "
|
||||
<< temperature_to_string[expected_wal_temperature]
|
||||
<< std::endl;
|
||||
assert(false);
|
||||
}
|
||||
} else if (opts.temperature != expected_other_metadata_temperature) {
|
||||
std::cerr << "Attempt to open " << fname << " with temperature "
|
||||
<< temperature_to_string[opts.temperature]
|
||||
<< " rather than "
|
||||
<< temperature_to_string[expected_wal_temperature]
|
||||
<< std::endl;
|
||||
assert(false);
|
||||
}
|
||||
UpdateCount(type, 1);
|
||||
}
|
||||
}
|
||||
return ios;
|
||||
}
|
||||
|
||||
IOStatus RenameFile(const std::string& src, const std::string& dst,
|
||||
const IOOptions& options,
|
||||
IODebugContext* dbg) override {
|
||||
IOStatus ios = FileTemperatureTestFS::RenameFile(src, dst, options, dbg);
|
||||
if (ios.ok()) {
|
||||
uint64_t number;
|
||||
FileType src_type;
|
||||
FileType dst_type;
|
||||
assert(ParseFileName(GetFileName(src), &number, "LOG", &src_type));
|
||||
assert(ParseFileName(GetFileName(dst), &number, "LOG", &dst_type));
|
||||
|
||||
UpdateCount(src_type, -1);
|
||||
UpdateCount(dst_type, 1);
|
||||
}
|
||||
return ios;
|
||||
}
|
||||
|
||||
void UpdateCount(FileType type, int delta) {
|
||||
size_t i = static_cast<size_t>(type);
|
||||
assert(i < kNumberFileTypes);
|
||||
counts[i].FetchAddRelaxed(delta);
|
||||
}
|
||||
|
||||
std::map<FileType, size_t> PopCounts() {
|
||||
std::map<FileType, size_t> ret;
|
||||
for (size_t i = 0; i < kNumberFileTypes; ++i) {
|
||||
int c = counts[i].ExchangeRelaxed(0);
|
||||
if (c > 0) {
|
||||
ret[static_cast<FileType>(i)] = c;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
FileOptions OptimizeForLogWrite(
|
||||
const FileOptions& file_options,
|
||||
const DBOptions& /*db_options*/) const override {
|
||||
FileOptions opts = file_options;
|
||||
if (optimize_wal_temperature != Temperature::kUnknown) {
|
||||
opts.temperature = optimize_wal_temperature;
|
||||
}
|
||||
return opts;
|
||||
}
|
||||
|
||||
FileOptions OptimizeForManifestWrite(
|
||||
const FileOptions& file_options) const override {
|
||||
FileOptions opts = file_options;
|
||||
if (optimize_manifest_temperature != Temperature::kUnknown) {
|
||||
opts.temperature = optimize_manifest_temperature;
|
||||
}
|
||||
return opts;
|
||||
}
|
||||
|
||||
void Reset() {
|
||||
optimize_manifest_temperature = Temperature::kUnknown;
|
||||
optimize_wal_temperature = Temperature::kUnknown;
|
||||
expected_manifest_temperature = Temperature::kUnknown;
|
||||
expected_other_metadata_temperature = Temperature::kUnknown;
|
||||
expected_wal_temperature = Temperature::kUnknown;
|
||||
for (auto& c : counts) {
|
||||
c.StoreRelaxed(0);
|
||||
}
|
||||
}
|
||||
|
||||
Temperature optimize_manifest_temperature;
|
||||
Temperature optimize_wal_temperature;
|
||||
Temperature expected_manifest_temperature;
|
||||
Temperature expected_other_metadata_temperature;
|
||||
Temperature expected_wal_temperature;
|
||||
std::array<RelaxedAtomic<int>, kNumberFileTypes> counts;
|
||||
};
|
||||
|
||||
// We don't have enough non-unknown temps to confidently distinguish that
|
||||
// a specific setting caused a specific outcome, in a single run. This is a
|
||||
// reasonable work-around without blowing up test time. Only returns
|
||||
// non-unknown temperatures.
|
||||
auto RandomTemp = [] {
|
||||
static std::vector<Temperature> temps = {
|
||||
Temperature::kHot, Temperature::kWarm, Temperature::kCold};
|
||||
return temps[Random::GetTLSInstance()->Uniform(
|
||||
static_cast<int>(temps.size()))];
|
||||
};
|
||||
|
||||
auto test_fs = std::make_shared<MyTestFS>(env_->GetFileSystem());
|
||||
std::unique_ptr<Env> env(new CompositeEnvWrapper(env_, test_fs));
|
||||
for (bool use_optimize : {false, true}) {
|
||||
std::cerr << "use_optimize: " << std::to_string(use_optimize) << std::endl;
|
||||
for (bool use_temp_options : {false, true}) {
|
||||
std::cerr << "use_temp_options: " << std::to_string(use_temp_options)
|
||||
<< std::endl;
|
||||
|
||||
Options options = CurrentOptions();
|
||||
// Currently require for last level temperature
|
||||
options.compaction_style = kCompactionStyleUniversal;
|
||||
options.env = env.get();
|
||||
test_fs->Reset();
|
||||
if (use_optimize) {
|
||||
test_fs->optimize_manifest_temperature = RandomTemp();
|
||||
test_fs->expected_manifest_temperature =
|
||||
test_fs->optimize_manifest_temperature;
|
||||
test_fs->optimize_wal_temperature = RandomTemp();
|
||||
test_fs->expected_wal_temperature = test_fs->optimize_wal_temperature;
|
||||
}
|
||||
if (use_temp_options) {
|
||||
options.metadata_write_temperature = RandomTemp();
|
||||
test_fs->expected_manifest_temperature =
|
||||
options.metadata_write_temperature;
|
||||
test_fs->expected_other_metadata_temperature =
|
||||
options.metadata_write_temperature;
|
||||
options.wal_write_temperature = RandomTemp();
|
||||
test_fs->expected_wal_temperature = options.wal_write_temperature;
|
||||
options.last_level_temperature = RandomTemp();
|
||||
options.default_write_temperature = RandomTemp();
|
||||
}
|
||||
|
||||
DestroyAndReopen(options);
|
||||
Defer closer([&] { Close(); });
|
||||
|
||||
using FTC = std::map<FileType, size_t>;
|
||||
// Files on DB startup
|
||||
ASSERT_EQ(test_fs->PopCounts(), FTC({{kWalFile, 1},
|
||||
{kDescriptorFile, 2},
|
||||
{kCurrentFile, 2},
|
||||
{kIdentityFile, 1},
|
||||
{kOptionsFile, 1}}));
|
||||
|
||||
// Temperature count map
|
||||
using TCM = std::map<Temperature, size_t>;
|
||||
ASSERT_EQ(test_fs->CountCurrentSstFilesByTemp(), TCM({}));
|
||||
|
||||
ASSERT_OK(Put("foo", "1"));
|
||||
ASSERT_OK(Put("bar", "1"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Put("foo", "2"));
|
||||
ASSERT_OK(Put("bar", "2"));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
ASSERT_EQ(test_fs->CountCurrentSstFilesByTemp(),
|
||||
TCM({{options.default_write_temperature, 2}}));
|
||||
|
||||
ASSERT_OK(db_->CompactRange({}, nullptr, nullptr));
|
||||
ASSERT_OK(dbfull()->TEST_WaitForBackgroundWork());
|
||||
|
||||
ASSERT_EQ(test_fs->CountCurrentSstFilesByTemp(),
|
||||
TCM({{options.last_level_temperature, 1}}));
|
||||
|
||||
ASSERT_OK(Put("foo", "3"));
|
||||
ASSERT_OK(Put("bar", "3"));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
// Just in memtable/WAL
|
||||
ASSERT_OK(Put("dog", "3"));
|
||||
|
||||
{
|
||||
TCM expected;
|
||||
expected[options.default_write_temperature] += 1;
|
||||
expected[options.last_level_temperature] += 1;
|
||||
ASSERT_EQ(test_fs->CountCurrentSstFilesByTemp(), expected);
|
||||
}
|
||||
|
||||
// New files during operation
|
||||
ASSERT_EQ(test_fs->PopCounts(), FTC({{kWalFile, 3}, {kTableFile, 4}}));
|
||||
|
||||
Reopen(options);
|
||||
|
||||
// New files during re-open/recovery
|
||||
ASSERT_EQ(test_fs->PopCounts(), FTC({{kWalFile, 1},
|
||||
{kTableFile, 1},
|
||||
{kDescriptorFile, 1},
|
||||
{kCurrentFile, 1},
|
||||
{kOptionsFile, 1}}));
|
||||
|
||||
Destroy(options);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBTest2, LastLevelTemperature) {
|
||||
class TestListener : public EventListener {
|
||||
public:
|
||||
|
||||
+10
-1
@@ -831,6 +831,15 @@ class FileTemperatureTestFS : public FileSystemWrapper {
|
||||
return count;
|
||||
}
|
||||
|
||||
std::map<Temperature, size_t> CountCurrentSstFilesByTemp() {
|
||||
MutexLock lock(&mu_);
|
||||
std::map<Temperature, size_t> ret;
|
||||
for (const auto& e : current_sst_file_temperatures_) {
|
||||
ret[e.second]++;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void OverrideSstFileTemperature(uint64_t number, Temperature temp) {
|
||||
MutexLock lock(&mu_);
|
||||
current_sst_file_temperatures_[number] = temp;
|
||||
@@ -842,7 +851,7 @@ class FileTemperatureTestFS : public FileSystemWrapper {
|
||||
requested_sst_file_temperatures_;
|
||||
std::map<uint64_t, Temperature> current_sst_file_temperatures_;
|
||||
|
||||
std::string GetFileName(const std::string& fname) {
|
||||
static std::string GetFileName(const std::string& fname) {
|
||||
auto filename = fname.substr(fname.find_last_of(kFilePathSeparator) + 1);
|
||||
// workaround only for Windows that the file path could contain both Windows
|
||||
// FilePathSeparator and '/'
|
||||
|
||||
@@ -2931,6 +2931,29 @@ TEST_F(DBWALTest, RecoveryFlushSwitchWALOnEmptyMemtable) {
|
||||
ASSERT_EQ("new_v", Get("k"));
|
||||
Destroy(options);
|
||||
}
|
||||
|
||||
TEST_F(DBWALTest, WALWriteErrorNoRecovery) {
|
||||
Options options = CurrentOptions();
|
||||
auto fault_fs = std::make_shared<FaultInjectionTestFS>(FileSystem::Default());
|
||||
std::unique_ptr<Env> fault_fs_env(NewCompositeEnv(fault_fs));
|
||||
options.env = fault_fs_env.get();
|
||||
options.manual_wal_flush = true;
|
||||
DestroyAndReopen(options);
|
||||
fault_fs->SetThreadLocalErrorContext(
|
||||
FaultInjectionIOType::kWrite, 7 /* seed*/, 1 /* one_in */,
|
||||
true /* retryable */, false /* has_data_loss*/);
|
||||
fault_fs->EnableThreadLocalErrorInjection(FaultInjectionIOType::kWrite);
|
||||
|
||||
ASSERT_OK(Put("k", "v"));
|
||||
Status s;
|
||||
s = db_->FlushWAL(false);
|
||||
ASSERT_TRUE(s.IsIOError());
|
||||
s = dbfull()->TEST_GetBGError();
|
||||
ASSERT_EQ(s.severity(), Status::Severity::kFatalError);
|
||||
ASSERT_FALSE(dbfull()->TEST_IsRecoveryInProgress());
|
||||
fault_fs->DisableThreadLocalErrorInjection(FaultInjectionIOType::kWrite);
|
||||
Destroy(options);
|
||||
}
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -172,6 +172,70 @@ TEST_F(DBBasicTestWithTimestamp, MixedCfs) {
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTestWithTimestamp, MultiGetMultipleCfs) {
|
||||
const size_t kTimestampSize = Timestamp(0, 0).size();
|
||||
TestComparator test_cmp(kTimestampSize);
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.create_if_missing = true;
|
||||
options.avoid_flush_during_shutdown = true;
|
||||
options.comparator = &test_cmp;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
Options options1 = CurrentOptions();
|
||||
options1.env = env_;
|
||||
options1.comparator = &test_cmp;
|
||||
ColumnFamilyHandle* handle = nullptr;
|
||||
Status s = db_->CreateColumnFamily(options1, "data", &handle);
|
||||
ASSERT_OK(s);
|
||||
|
||||
std::string ts = Timestamp(1, 0);
|
||||
WriteBatch wb(0, 0, 0, kTimestampSize);
|
||||
ASSERT_OK(wb.Put("a", "value"));
|
||||
ASSERT_OK(wb.Put(handle, "a", "value"));
|
||||
const auto ts_sz_func = [kTimestampSize](uint32_t /*cf_id*/) {
|
||||
return kTimestampSize;
|
||||
};
|
||||
ASSERT_OK(wb.UpdateTimestamps(ts, ts_sz_func));
|
||||
ASSERT_OK(db_->Write(WriteOptions(), &wb));
|
||||
|
||||
int num_keys = 2;
|
||||
std::vector<Slice> keys;
|
||||
std::vector<std::string> expected_values;
|
||||
for (int i = 0; i < num_keys; i++) {
|
||||
keys.push_back("a");
|
||||
expected_values.push_back("value");
|
||||
}
|
||||
std::vector<ColumnFamilyHandle*> handles;
|
||||
handles.push_back(db_->DefaultColumnFamily());
|
||||
handles.push_back(handle);
|
||||
|
||||
{
|
||||
Slice read_ts_slice(ts);
|
||||
ReadOptions read_opts;
|
||||
read_opts.timestamp = &read_ts_slice;
|
||||
|
||||
std::vector<PinnableSlice> values;
|
||||
values.resize(num_keys);
|
||||
std::vector<Status> statuses;
|
||||
statuses.resize(num_keys);
|
||||
std::vector<std::string> timestamps;
|
||||
timestamps.resize(num_keys);
|
||||
|
||||
db_->MultiGet(read_opts, num_keys, handles.data(), keys.data(),
|
||||
values.data(), timestamps.data(), statuses.data());
|
||||
|
||||
for (int i = 0; i < num_keys; i++) {
|
||||
ASSERT_OK(statuses[i]);
|
||||
ASSERT_EQ(expected_values[i], values[i].ToString());
|
||||
ASSERT_EQ(ts, timestamps[i]);
|
||||
}
|
||||
}
|
||||
|
||||
delete handle;
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTestWithTimestamp, CompactRangeWithSpecifiedRange) {
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
@@ -768,6 +832,7 @@ TEST_P(DBBasicTestWithTimestampTableOptions, GetAndMultiGet) {
|
||||
|
||||
TEST_P(DBBasicTestWithTimestampTableOptions, SeekWithPrefixLessThanKey) {
|
||||
Options options = CurrentOptions();
|
||||
options.prefix_seek_opt_in_only = false; // Use legacy prefix seek
|
||||
options.env = env_;
|
||||
options.create_if_missing = true;
|
||||
options.prefix_extractor.reset(NewFixedPrefixTransform(3));
|
||||
@@ -945,6 +1010,7 @@ TEST_F(DBBasicTestWithTimestamp, ChangeIterationDirection) {
|
||||
TestComparator test_cmp(kTimestampSize);
|
||||
options.comparator = &test_cmp;
|
||||
options.prefix_extractor.reset(NewFixedPrefixTransform(1));
|
||||
options.prefix_seek_opt_in_only = false; // Use legacy prefix seek
|
||||
options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
|
||||
DestroyAndReopen(options);
|
||||
const std::vector<std::string> timestamps = {Timestamp(1, 1), Timestamp(0, 2),
|
||||
|
||||
+28
-3
@@ -381,7 +381,7 @@ void ErrorHandler::HandleKnownErrors(const Status& bg_err,
|
||||
// BackgroundErrorReason reason) will be called to handle other error cases
|
||||
// such as delegating to SstFileManager to handle no space error.
|
||||
void ErrorHandler::SetBGError(const Status& bg_status,
|
||||
BackgroundErrorReason reason) {
|
||||
BackgroundErrorReason reason, bool wal_related) {
|
||||
db_mutex_->AssertHeld();
|
||||
Status tmp_status = bg_status;
|
||||
IOStatus bg_io_err = status_to_io_status(std::move(tmp_status));
|
||||
@@ -389,8 +389,8 @@ void ErrorHandler::SetBGError(const Status& bg_status,
|
||||
if (bg_io_err.ok()) {
|
||||
return;
|
||||
}
|
||||
ROCKS_LOG_WARN(db_options_.info_log, "Background IO error %s",
|
||||
bg_io_err.ToString().c_str());
|
||||
ROCKS_LOG_WARN(db_options_.info_log, "Background IO error %s, reason %d",
|
||||
bg_io_err.ToString().c_str(), static_cast<int>(reason));
|
||||
|
||||
RecordStats({ERROR_HANDLER_BG_ERROR_COUNT, ERROR_HANDLER_BG_IO_ERROR_COUNT},
|
||||
{} /* int_histograms */);
|
||||
@@ -412,6 +412,31 @@ void ErrorHandler::SetBGError(const Status& bg_status,
|
||||
recover_context_ = context;
|
||||
return;
|
||||
}
|
||||
if (wal_related) {
|
||||
assert(reason == BackgroundErrorReason::kWriteCallback ||
|
||||
reason == BackgroundErrorReason::kMemTable ||
|
||||
reason == BackgroundErrorReason::kFlush);
|
||||
}
|
||||
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.
|
||||
bool auto_recovery = false;
|
||||
Status bg_err(new_bg_io_err, Status::Severity::kFatalError);
|
||||
CheckAndSetRecoveryAndBGError(bg_err);
|
||||
ROCKS_LOG_WARN(db_options_.info_log,
|
||||
"ErrorHandler: A potentially WAL error happened, set "
|
||||
"background IO error as fatal error\n");
|
||||
EventHelpers::NotifyOnBackgroundError(db_options_.listeners, reason,
|
||||
&bg_err, db_mutex_, &auto_recovery);
|
||||
recover_context_ = context;
|
||||
return;
|
||||
}
|
||||
|
||||
if (bg_io_err.subcode() != IOStatus::SubCode::kNoSpace &&
|
||||
(bg_io_err.GetScope() == IOStatus::IOErrorScope::kIOErrorScopeFile ||
|
||||
bg_io_err.GetRetryable())) {
|
||||
|
||||
+2
-1
@@ -56,7 +56,8 @@ class ErrorHandler {
|
||||
Status::Severity GetErrorSeverity(BackgroundErrorReason reason,
|
||||
Status::Code code, Status::SubCode subcode);
|
||||
|
||||
void SetBGError(const Status& bg_err, BackgroundErrorReason reason);
|
||||
void SetBGError(const Status& bg_err, BackgroundErrorReason reason,
|
||||
bool wal_related = false);
|
||||
|
||||
Status GetBGError() const { return bg_error_; }
|
||||
|
||||
|
||||
@@ -124,6 +124,7 @@ void EventHelpers::LogAndNotifyTableFileCreationFinished(
|
||||
<< "comparator" << table_properties.comparator_name
|
||||
<< "user_defined_timestamps_persisted"
|
||||
<< table_properties.user_defined_timestamps_persisted
|
||||
<< "key_largest_seqno" << table_properties.key_largest_seqno
|
||||
<< "merge_operator" << table_properties.merge_operator_name
|
||||
<< "prefix_extractor_name"
|
||||
<< table_properties.prefix_extractor_name << "property_collectors"
|
||||
|
||||
+165
-33
@@ -152,6 +152,85 @@ Status UpdateManifestForFilesState(
|
||||
// EXPERIMENTAL new filtering features
|
||||
|
||||
namespace {
|
||||
template <size_t N>
|
||||
class SemiStaticCappedKeySegmentsExtractor : public KeySegmentsExtractor {
|
||||
public:
|
||||
SemiStaticCappedKeySegmentsExtractor(const uint32_t* byte_widths) {
|
||||
id_ = kName();
|
||||
uint32_t prev_end = 0;
|
||||
if constexpr (N > 0) { // Suppress a compiler warning
|
||||
for (size_t i = 0; i < N; ++i) {
|
||||
prev_end = prev_end + byte_widths[i];
|
||||
ideal_ends_[i] = prev_end;
|
||||
id_ += std::to_string(byte_widths[i]) + "b";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static const char* kName() { return "CappedKeySegmentsExtractor"; }
|
||||
|
||||
const char* Name() const override { return kName(); }
|
||||
|
||||
std::string GetId() const override { return id_; }
|
||||
|
||||
void Extract(const Slice& key_or_bound, KeyKind /*kind*/,
|
||||
Result* result) const override {
|
||||
// Optimistic assignment
|
||||
result->segment_ends.assign(ideal_ends_.begin(), ideal_ends_.end());
|
||||
if constexpr (N > 0) { // Suppress a compiler warning
|
||||
uint32_t key_size = static_cast<uint32_t>(key_or_bound.size());
|
||||
if (key_size < ideal_ends_.back()) {
|
||||
// Need to fix up (should be rare)
|
||||
for (size_t i = 0; i < N; ++i) {
|
||||
result->segment_ends[i] = std::min(key_size, result->segment_ends[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::array<uint32_t, N> ideal_ends_;
|
||||
std::string id_;
|
||||
};
|
||||
|
||||
class DynamicCappedKeySegmentsExtractor : public KeySegmentsExtractor {
|
||||
public:
|
||||
DynamicCappedKeySegmentsExtractor(const std::vector<uint32_t>& byte_widths) {
|
||||
id_ = kName();
|
||||
uint32_t prev_end = 0;
|
||||
for (size_t i = 0; i < byte_widths.size(); ++i) {
|
||||
prev_end = prev_end + byte_widths[i];
|
||||
ideal_ends_[i] = prev_end;
|
||||
id_ += std::to_string(byte_widths[i]) + "b";
|
||||
}
|
||||
final_ideal_end_ = prev_end;
|
||||
}
|
||||
|
||||
static const char* kName() { return "CappedKeySegmentsExtractor"; }
|
||||
|
||||
const char* Name() const override { return kName(); }
|
||||
|
||||
std::string GetId() const override { return id_; }
|
||||
|
||||
void Extract(const Slice& key_or_bound, KeyKind /*kind*/,
|
||||
Result* result) const override {
|
||||
// Optimistic assignment
|
||||
result->segment_ends = ideal_ends_;
|
||||
uint32_t key_size = static_cast<uint32_t>(key_or_bound.size());
|
||||
if (key_size < final_ideal_end_) {
|
||||
// Need to fix up (should be rare)
|
||||
for (size_t i = 0; i < ideal_ends_.size(); ++i) {
|
||||
result->segment_ends[i] = std::min(key_size, result->segment_ends[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<uint32_t> ideal_ends_;
|
||||
uint32_t final_ideal_end_;
|
||||
std::string id_;
|
||||
};
|
||||
|
||||
void GetFilterInput(FilterInput select, const Slice& key,
|
||||
const KeySegmentsExtractor::Result& extracted,
|
||||
Slice* out_input, Slice* out_leadup) {
|
||||
@@ -211,12 +290,6 @@ void GetFilterInput(FilterInput select, const Slice& key,
|
||||
assert(false);
|
||||
return Slice();
|
||||
}
|
||||
|
||||
Slice operator()(SelectValue) {
|
||||
// TODO
|
||||
assert(false);
|
||||
return Slice();
|
||||
}
|
||||
};
|
||||
|
||||
Slice input = std::visit(FilterInputGetter(key, extracted), select);
|
||||
@@ -256,9 +329,6 @@ const char* DeserializeFilterInput(const char* p, const char* limit,
|
||||
case 3:
|
||||
*out = SelectColumnName{};
|
||||
return p;
|
||||
case 4:
|
||||
*out = SelectValue{};
|
||||
return p;
|
||||
default:
|
||||
// Reserved for future use
|
||||
return nullptr;
|
||||
@@ -315,7 +385,6 @@ void SerializeFilterInput(std::string* out, const FilterInput& select) {
|
||||
void operator()(SelectLegacyKeyPrefix) { out->push_back(1); }
|
||||
void operator()(SelectUserTimestamp) { out->push_back(2); }
|
||||
void operator()(SelectColumnName) { out->push_back(3); }
|
||||
void operator()(SelectValue) { out->push_back(4); }
|
||||
void operator()(SelectKeySegment select) {
|
||||
// TODO: expand supported cases
|
||||
assert(select.segment_index < 16);
|
||||
@@ -372,6 +441,7 @@ enum BuiltinSstQueryFilters : char {
|
||||
// and filtered independently because it might be a special case that is
|
||||
// not representative of the minimum in a spread of values.
|
||||
kBytewiseMinMaxFilter = 0x10,
|
||||
kRevBytewiseMinMaxFilter = 0x11,
|
||||
};
|
||||
|
||||
class SstQueryFilterBuilder {
|
||||
@@ -459,7 +529,10 @@ class CategoryScopeFilterWrapperBuilder : public SstQueryFilterBuilder {
|
||||
|
||||
class BytewiseMinMaxSstQueryFilterConfig : public SstQueryFilterConfigImpl {
|
||||
public:
|
||||
using SstQueryFilterConfigImpl::SstQueryFilterConfigImpl;
|
||||
explicit BytewiseMinMaxSstQueryFilterConfig(
|
||||
const FilterInput& input,
|
||||
const KeySegmentsExtractor::KeyCategorySet& categories, bool reverse)
|
||||
: SstQueryFilterConfigImpl(input, categories), reverse_(reverse) {}
|
||||
|
||||
std::unique_ptr<SstQueryFilterBuilder> NewBuilder(
|
||||
bool sanity_checks) const override {
|
||||
@@ -477,11 +550,13 @@ class BytewiseMinMaxSstQueryFilterConfig : public SstQueryFilterConfigImpl {
|
||||
const KeySegmentsExtractor::Result& lower_bound_extracted,
|
||||
const Slice& upper_bound_excl,
|
||||
const KeySegmentsExtractor::Result& upper_bound_extracted) {
|
||||
assert(!filter.empty() && filter[0] == kBytewiseMinMaxFilter);
|
||||
assert(!filter.empty() && (filter[0] == kBytewiseMinMaxFilter ||
|
||||
filter[0] == kRevBytewiseMinMaxFilter));
|
||||
if (filter.size() <= 4) {
|
||||
// Missing some data
|
||||
return true;
|
||||
}
|
||||
bool reverse = (filter[0] == kRevBytewiseMinMaxFilter);
|
||||
bool empty_included = (filter[1] & kEmptySeenFlag) != 0;
|
||||
const char* p = filter.data() + 2;
|
||||
const char* limit = filter.data() + filter.size();
|
||||
@@ -528,8 +603,13 @@ class BytewiseMinMaxSstQueryFilterConfig : public SstQueryFilterConfigImpl {
|
||||
|
||||
// May match if both the upper bound and lower bound indicate there could
|
||||
// be overlap
|
||||
return upper_bound_input.compare(smallest) >= 0 &&
|
||||
lower_bound_input.compare(largest) <= 0;
|
||||
if (reverse) {
|
||||
return upper_bound_input.compare(smallest) <= 0 &&
|
||||
lower_bound_input.compare(largest) >= 0;
|
||||
} else {
|
||||
return upper_bound_input.compare(smallest) >= 0 &&
|
||||
lower_bound_input.compare(largest) <= 0;
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
@@ -551,19 +631,11 @@ class BytewiseMinMaxSstQueryFilterConfig : public SstQueryFilterConfigImpl {
|
||||
&prev_leadup);
|
||||
|
||||
int compare = prev_leadup.compare(leadup);
|
||||
if (compare > 0) {
|
||||
status = Status::Corruption(
|
||||
"Ordering invariant violated from 0x" +
|
||||
prev_key->ToString(/*hex=*/true) + " with prefix 0x" +
|
||||
prev_leadup.ToString(/*hex=*/true) + " to 0x" +
|
||||
key.ToString(/*hex=*/true) + " with prefix 0x" +
|
||||
leadup.ToString(/*hex=*/true));
|
||||
return;
|
||||
} else if (compare == 0) {
|
||||
if (compare == 0) {
|
||||
// On the same prefix leading up to the segment, the segments must
|
||||
// not be out of order.
|
||||
compare = prev_input.compare(input);
|
||||
if (compare > 0) {
|
||||
if (parent.reverse_ ? compare < 0 : compare > 0) {
|
||||
status = Status::Corruption(
|
||||
"Ordering invariant violated from 0x" +
|
||||
prev_key->ToString(/*hex=*/true) + " with segment 0x" +
|
||||
@@ -573,6 +645,9 @@ class BytewiseMinMaxSstQueryFilterConfig : public SstQueryFilterConfigImpl {
|
||||
return;
|
||||
}
|
||||
}
|
||||
// NOTE: it is not strictly required that the leadup be ordered, just
|
||||
// satisfy the "common segment prefix property" which would be
|
||||
// expensive to check
|
||||
}
|
||||
|
||||
// Now actually update state for the filter inputs
|
||||
@@ -598,7 +673,8 @@ class BytewiseMinMaxSstQueryFilterConfig : public SstQueryFilterConfigImpl {
|
||||
return 0;
|
||||
}
|
||||
return 2 + GetFilterInputSerializedLength(parent.input_) +
|
||||
VarintLength(smallest.size()) + smallest.size() + largest.size();
|
||||
VarintLength(parent.reverse_ ? largest.size() : smallest.size()) +
|
||||
smallest.size() + largest.size();
|
||||
}
|
||||
|
||||
void Finish(std::string& append_to) override {
|
||||
@@ -610,23 +686,27 @@ class BytewiseMinMaxSstQueryFilterConfig : public SstQueryFilterConfigImpl {
|
||||
}
|
||||
size_t old_append_to_size = append_to.size();
|
||||
append_to.reserve(old_append_to_size + encoded_length);
|
||||
append_to.push_back(kBytewiseMinMaxFilter);
|
||||
append_to.push_back(parent.reverse_ ? kRevBytewiseMinMaxFilter
|
||||
: kBytewiseMinMaxFilter);
|
||||
|
||||
append_to.push_back(empty_seen ? kEmptySeenFlag : 0);
|
||||
|
||||
SerializeFilterInput(&append_to, parent.input_);
|
||||
|
||||
PutVarint32(&append_to, static_cast<uint32_t>(smallest.size()));
|
||||
append_to.append(smallest);
|
||||
// The end of `largest` is given by the end of the filter
|
||||
append_to.append(largest);
|
||||
auto& minv = parent.reverse_ ? largest : smallest;
|
||||
auto& maxv = parent.reverse_ ? smallest : largest;
|
||||
PutVarint32(&append_to, static_cast<uint32_t>(minv.size()));
|
||||
append_to.append(minv);
|
||||
// The end of `maxv` is given by the end of the filter
|
||||
append_to.append(maxv);
|
||||
assert(append_to.size() == old_append_to_size + encoded_length);
|
||||
}
|
||||
|
||||
const BytewiseMinMaxSstQueryFilterConfig& parent;
|
||||
const bool sanity_checks;
|
||||
// Smallest and largest segment seen, excluding the empty segment which
|
||||
// is tracked separately
|
||||
// is tracked separately. "Reverse" from parent is only applied at
|
||||
// serialization time, for efficiency.
|
||||
std::string smallest;
|
||||
std::string largest;
|
||||
bool empty_seen = false;
|
||||
@@ -635,6 +715,8 @@ class BytewiseMinMaxSstQueryFilterConfig : public SstQueryFilterConfigImpl {
|
||||
Status status;
|
||||
};
|
||||
|
||||
bool reverse_;
|
||||
|
||||
private:
|
||||
static constexpr char kEmptySeenFlag = 0x1;
|
||||
};
|
||||
@@ -1036,6 +1118,7 @@ class SstQueryFilterConfigsManagerImpl : public SstQueryFilterConfigsManager {
|
||||
may_match = MayMatch_CategoryScopeFilterWrapper(filter, *state);
|
||||
break;
|
||||
case kBytewiseMinMaxFilter:
|
||||
case kRevBytewiseMinMaxFilter:
|
||||
if (state == nullptr) {
|
||||
// TODO? Report problem
|
||||
// No filtering
|
||||
@@ -1189,14 +1272,63 @@ const std::string SstQueryFilterConfigsManagerImpl::kTablePropertyName =
|
||||
"rocksdb.sqfc";
|
||||
} // namespace
|
||||
|
||||
std::shared_ptr<const KeySegmentsExtractor>
|
||||
MakeSharedCappedKeySegmentsExtractor(const std::vector<size_t>& byte_widths) {
|
||||
std::vector<uint32_t> byte_widths_checked;
|
||||
byte_widths_checked.resize(byte_widths.size());
|
||||
size_t final_end = 0;
|
||||
for (size_t i = 0; i < byte_widths.size(); ++i) {
|
||||
final_end += byte_widths[i];
|
||||
if (byte_widths[i] > UINT32_MAX / 2 || final_end > UINT32_MAX) {
|
||||
// Better to crash than to proceed unsafely
|
||||
return nullptr;
|
||||
}
|
||||
byte_widths_checked[i] = static_cast<uint32_t>(byte_widths[i]);
|
||||
}
|
||||
|
||||
switch (byte_widths_checked.size()) {
|
||||
case 0:
|
||||
return std::make_shared<SemiStaticCappedKeySegmentsExtractor<0>>(
|
||||
byte_widths_checked.data());
|
||||
case 1:
|
||||
return std::make_shared<SemiStaticCappedKeySegmentsExtractor<1>>(
|
||||
byte_widths_checked.data());
|
||||
case 2:
|
||||
return std::make_shared<SemiStaticCappedKeySegmentsExtractor<2>>(
|
||||
byte_widths_checked.data());
|
||||
case 3:
|
||||
return std::make_shared<SemiStaticCappedKeySegmentsExtractor<3>>(
|
||||
byte_widths_checked.data());
|
||||
case 4:
|
||||
return std::make_shared<SemiStaticCappedKeySegmentsExtractor<4>>(
|
||||
byte_widths_checked.data());
|
||||
case 5:
|
||||
return std::make_shared<SemiStaticCappedKeySegmentsExtractor<5>>(
|
||||
byte_widths_checked.data());
|
||||
case 6:
|
||||
return std::make_shared<SemiStaticCappedKeySegmentsExtractor<6>>(
|
||||
byte_widths_checked.data());
|
||||
default:
|
||||
return std::make_shared<DynamicCappedKeySegmentsExtractor>(
|
||||
byte_widths_checked);
|
||||
}
|
||||
}
|
||||
|
||||
bool SstQueryFilterConfigs::IsEmptyNotFound() const {
|
||||
return this == &kEmptyNotFoundSQFC;
|
||||
}
|
||||
|
||||
std::shared_ptr<SstQueryFilterConfig> MakeSharedBytewiseMinMaxSQFC(
|
||||
FilterInput input, KeySegmentsExtractor::KeyCategorySet categories) {
|
||||
return std::make_shared<BytewiseMinMaxSstQueryFilterConfig>(input,
|
||||
categories);
|
||||
return std::make_shared<BytewiseMinMaxSstQueryFilterConfig>(
|
||||
input, categories,
|
||||
/*reverse=*/false);
|
||||
}
|
||||
|
||||
std::shared_ptr<SstQueryFilterConfig> MakeSharedReverseBytewiseMinMaxSQFC(
|
||||
FilterInput input, KeySegmentsExtractor::KeyCategorySet categories) {
|
||||
return std::make_shared<BytewiseMinMaxSstQueryFilterConfig>(input, categories,
|
||||
/*reverse=*/true);
|
||||
}
|
||||
|
||||
Status SstQueryFilterConfigsManager::MakeShared(
|
||||
|
||||
@@ -113,8 +113,7 @@ Status ExternalSstFileIngestionJob::Prepare(
|
||||
const std::string path_outside_db = f.external_file_path;
|
||||
const std::string path_inside_db = TableFileName(
|
||||
cfd_->ioptions()->cf_paths, f.fd.GetNumber(), f.fd.GetPathId());
|
||||
if (ingestion_options_.move_files) {
|
||||
assert(!ingestion_options_.allow_db_generated_files);
|
||||
if (ingestion_options_.move_files || ingestion_options_.link_files) {
|
||||
status =
|
||||
fs_->LinkFile(path_outside_db, path_inside_db, IOOptions(), nullptr);
|
||||
if (status.ok()) {
|
||||
@@ -914,9 +913,18 @@ Status ExternalSstFileIngestionJob::GetIngestedFileInfo(
|
||||
} else if (!iter->status().ok()) {
|
||||
return iter->status();
|
||||
}
|
||||
if (ingestion_options_.allow_db_generated_files) {
|
||||
// Verify that all keys have seqno zero.
|
||||
// TODO: store largest seqno in table property and validate it instead.
|
||||
SequenceNumber largest_seqno =
|
||||
table_reader.get()->GetTableProperties()->key_largest_seqno;
|
||||
// UINT64_MAX means unknown and the file is generated before table property
|
||||
// `key_largest_seqno` is introduced.
|
||||
if (largest_seqno != UINT64_MAX && largest_seqno > 0) {
|
||||
return Status::Corruption(
|
||||
"External file has non zero largest sequence number " +
|
||||
std::to_string(largest_seqno));
|
||||
}
|
||||
if (ingestion_options_.allow_db_generated_files &&
|
||||
largest_seqno == UINT64_MAX) {
|
||||
// Need to verify that all keys have seqno zero.
|
||||
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
|
||||
Status pik_status =
|
||||
ParseInternalKey(iter->key(), &key, allow_data_in_errors);
|
||||
|
||||
@@ -66,7 +66,7 @@ class ExternalSSTFileTestBase : public DBTestBase {
|
||||
|
||||
class ExternSSTFileLinkFailFallbackTest
|
||||
: public ExternalSSTFileTestBase,
|
||||
public ::testing::WithParamInterface<std::tuple<bool, bool>> {
|
||||
public ::testing::WithParamInterface<std::tuple<bool, bool, bool>> {
|
||||
public:
|
||||
ExternSSTFileLinkFailFallbackTest() {
|
||||
fs_ = std::make_shared<ExternalSSTTestFS>(env_->GetFileSystem(), true);
|
||||
@@ -2210,7 +2210,8 @@ TEST_P(ExternSSTFileLinkFailFallbackTest, LinkFailFallBackExternalSst) {
|
||||
DestroyAndReopen(options_);
|
||||
const int kNumKeys = 10000;
|
||||
IngestExternalFileOptions ifo;
|
||||
ifo.move_files = true;
|
||||
ifo.move_files = std::get<2>(GetParam());
|
||||
ifo.link_files = !ifo.move_files;
|
||||
ifo.failed_move_fall_back_to_copy = failed_move_fall_back_to_copy;
|
||||
|
||||
std::string file_path = sst_files_dir_ + "file1.sst";
|
||||
@@ -2251,6 +2252,13 @@ TEST_P(ExternSSTFileLinkFailFallbackTest, LinkFailFallBackExternalSst) {
|
||||
ASSERT_EQ(0, bytes_copied);
|
||||
ASSERT_EQ(file_size, bytes_moved);
|
||||
ASSERT_FALSE(copyfile);
|
||||
|
||||
Status es = env_->FileExists(file_path);
|
||||
if (ifo.move_files) {
|
||||
ASSERT_TRUE(es.IsNotFound());
|
||||
} else {
|
||||
ASSERT_OK(es);
|
||||
}
|
||||
} else {
|
||||
// Link operation fails.
|
||||
ASSERT_EQ(0, bytes_moved);
|
||||
@@ -2269,6 +2277,11 @@ TEST_P(ExternSSTFileLinkFailFallbackTest, LinkFailFallBackExternalSst) {
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(ExternSSTFileLinkFailFallbackTest,
|
||||
ExternSSTFileLinkFailFallbackTest,
|
||||
testing::Combine(testing::Bool(), testing::Bool(),
|
||||
testing::Bool()));
|
||||
|
||||
class TestIngestExternalFileListener : public EventListener {
|
||||
public:
|
||||
void OnExternalFileIngested(DB* /*db*/,
|
||||
@@ -3719,19 +3732,14 @@ TEST_F(ExternalSSTFileWithTimestampTest, TimestampsNotPersistedBasic) {
|
||||
INSTANTIATE_TEST_CASE_P(ExternalSSTFileTest, ExternalSSTFileTest,
|
||||
testing::Combine(testing::Bool(), testing::Bool()));
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(ExternSSTFileLinkFailFallbackTest,
|
||||
ExternSSTFileLinkFailFallbackTest,
|
||||
testing::Values(std::make_tuple(true, false),
|
||||
std::make_tuple(true, true),
|
||||
std::make_tuple(false, false)));
|
||||
|
||||
class IngestDBGeneratedFileTest : public ExternalSSTFileTestBase,
|
||||
public ::testing::WithParamInterface<bool> {
|
||||
class IngestDBGeneratedFileTest
|
||||
: public ExternalSSTFileTestBase,
|
||||
public ::testing::WithParamInterface<std::tuple<bool, bool>> {
|
||||
public:
|
||||
IngestDBGeneratedFileTest() {
|
||||
ingest_opts.allow_db_generated_files = true;
|
||||
ingest_opts.move_files = false;
|
||||
ingest_opts.verify_checksums_before_ingest = GetParam();
|
||||
ingest_opts.link_files = std::get<0>(GetParam());
|
||||
ingest_opts.verify_checksums_before_ingest = std::get<1>(GetParam());
|
||||
ingest_opts.snapshot_consistency = false;
|
||||
}
|
||||
|
||||
@@ -3740,9 +3748,16 @@ class IngestDBGeneratedFileTest : public ExternalSSTFileTestBase,
|
||||
};
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(BasicMultiConfig, IngestDBGeneratedFileTest,
|
||||
testing::Bool());
|
||||
testing::Combine(testing::Bool(), testing::Bool()));
|
||||
|
||||
TEST_P(IngestDBGeneratedFileTest, FailureCase) {
|
||||
if (encrypted_env_ && ingest_opts.link_files) {
|
||||
// FIXME: should fail ingestion or support this combination.
|
||||
ROCKSDB_GTEST_SKIP(
|
||||
"Encrypted env and link_files do not work together, as we reopen the "
|
||||
"file after linking it which appends an extra encryption prefix.");
|
||||
return;
|
||||
}
|
||||
// Ingesting overlapping data should always fail.
|
||||
do {
|
||||
SCOPED_TRACE("option_config_ = " + std::to_string(option_config_));
|
||||
@@ -3776,6 +3791,7 @@ TEST_P(IngestDBGeneratedFileTest, FailureCase) {
|
||||
live_meta[0].relative_filename);
|
||||
// Ingesting a file whose boundary key has non-zero seqno.
|
||||
Status s = db_->IngestExternalFile(to_ingest_files, ingest_opts);
|
||||
// This error msg is from checking seqno of boundary keys.
|
||||
ASSERT_TRUE(
|
||||
s.ToString().find("External file has non zero sequence number") !=
|
||||
std::string::npos);
|
||||
@@ -3822,10 +3838,9 @@ TEST_P(IngestDBGeneratedFileTest, FailureCase) {
|
||||
live_meta[0].directory + "/" + live_meta[0].relative_filename;
|
||||
s = db_->IngestExternalFile(to_ingest_files, ingest_opts);
|
||||
ASSERT_NOK(s);
|
||||
ASSERT_TRUE(
|
||||
s.ToString().find(
|
||||
"External file has a key with non zero sequence number") !=
|
||||
std::string::npos);
|
||||
// This error msg is from checking largest seqno in table property.
|
||||
ASSERT_TRUE(s.ToString().find("non zero largest sequence number") !=
|
||||
std::string::npos);
|
||||
db_->ReleaseSnapshot(snapshot);
|
||||
}
|
||||
|
||||
@@ -3895,14 +3910,6 @@ TEST_P(IngestDBGeneratedFileTest, FailureCase) {
|
||||
ASSERT_TRUE(s.ToString().find(err) != std::string::npos);
|
||||
ASSERT_NOK(s);
|
||||
|
||||
ingest_opts.move_files = true;
|
||||
s = db_->IngestExternalFile(to_ingest_files, ingest_opts);
|
||||
ingest_opts.move_files = false;
|
||||
ASSERT_TRUE(
|
||||
s.ToString().find("Options move_files and allow_db_generated_files are "
|
||||
"not compatible") != std::string::npos);
|
||||
ASSERT_NOK(s);
|
||||
|
||||
ingest_opts.snapshot_consistency = false;
|
||||
ASSERT_OK(db_->IngestExternalFile(to_ingest_files, ingest_opts));
|
||||
db_->ReleaseSnapshot(snapshot);
|
||||
@@ -3922,14 +3929,16 @@ TEST_P(IngestDBGeneratedFileTest, FailureCase) {
|
||||
|
||||
class IngestDBGeneratedFileTest2
|
||||
: public ExternalSSTFileTestBase,
|
||||
public ::testing::WithParamInterface<std::tuple<bool, bool, bool, bool>> {
|
||||
public ::testing::WithParamInterface<
|
||||
std::tuple<bool, bool, bool, bool, bool>> {
|
||||
public:
|
||||
IngestDBGeneratedFileTest2() = default;
|
||||
};
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(VaryingOptions, IngestDBGeneratedFileTest2,
|
||||
testing::Combine(testing::Bool(), testing::Bool(),
|
||||
testing::Bool(), testing::Bool()));
|
||||
testing::Bool(), testing::Bool(),
|
||||
testing::Bool()));
|
||||
|
||||
TEST_P(IngestDBGeneratedFileTest2, NotOverlapWithDB) {
|
||||
// Use a separate column family to sort some data, generate multiple SST
|
||||
@@ -3937,11 +3946,11 @@ TEST_P(IngestDBGeneratedFileTest2, NotOverlapWithDB) {
|
||||
// to be ingested does not overlap with existing data.
|
||||
IngestExternalFileOptions ingest_opts;
|
||||
ingest_opts.allow_db_generated_files = true;
|
||||
ingest_opts.move_files = false;
|
||||
ingest_opts.snapshot_consistency = std::get<0>(GetParam());
|
||||
ingest_opts.allow_global_seqno = std::get<1>(GetParam());
|
||||
ingest_opts.allow_blocking_flush = std::get<2>(GetParam());
|
||||
ingest_opts.fail_if_not_bottommost_level = std::get<3>(GetParam());
|
||||
ingest_opts.link_files = std::get<4>(GetParam());
|
||||
|
||||
do {
|
||||
SCOPED_TRACE("option_config_ = " + std::to_string(option_config_));
|
||||
|
||||
+11
-6
@@ -421,8 +421,8 @@ Status FlushJob::MemPurge() {
|
||||
std::vector<std::unique_ptr<FragmentedRangeTombstoneIterator>>
|
||||
range_del_iters;
|
||||
for (MemTable* m : mems_) {
|
||||
memtables.push_back(
|
||||
m->NewIterator(ro, /*seqno_to_time_mapping=*/nullptr, &arena));
|
||||
memtables.push_back(m->NewIterator(ro, /*seqno_to_time_mapping=*/nullptr,
|
||||
&arena, /*prefix_extractor=*/nullptr));
|
||||
auto* range_del_iter = m->NewRangeTombstoneIterator(
|
||||
ro, kMaxSequenceNumber, true /* immutable_memtable */);
|
||||
if (range_del_iter != nullptr) {
|
||||
@@ -630,7 +630,7 @@ Status FlushJob::MemPurge() {
|
||||
new_mem->SetNextLogNumber(mems_[0]->GetNextLogNumber());
|
||||
|
||||
// This addition will not trigger another flush, because
|
||||
// we do not call SchedulePendingFlush().
|
||||
// we do not call EnqueuePendingFlush().
|
||||
cfd_->imm()->Add(new_mem, &job_context_->memtables_to_free);
|
||||
new_mem->Ref();
|
||||
// Piggyback FlushJobInfo on the first flushed memtable.
|
||||
@@ -861,7 +861,7 @@ Status FlushJob::WriteLevel0Table() {
|
||||
std::vector<BlobFileAddition> blob_file_additions;
|
||||
|
||||
{
|
||||
auto write_hint = cfd_->CalculateSSTWriteHint(0);
|
||||
auto write_hint = base_->storage_info()->CalculateSSTWriteHint(/*level=*/0);
|
||||
Env::IOPriority io_priority = GetRateLimiterPriority();
|
||||
db_mutex_->Unlock();
|
||||
if (log_buffer_) {
|
||||
@@ -893,8 +893,8 @@ Status FlushJob::WriteLevel0Table() {
|
||||
db_options_.info_log,
|
||||
"[%s] [JOB %d] Flushing memtable with next log file: %" PRIu64 "\n",
|
||||
cfd_->GetName().c_str(), job_context_->job_id, m->GetNextLogNumber());
|
||||
memtables.push_back(
|
||||
m->NewIterator(ro, /*seqno_to_time_mapping=*/nullptr, &arena));
|
||||
memtables.push_back(m->NewIterator(ro, /*seqno_to_time_mapping=*/nullptr,
|
||||
&arena, /*prefix_extractor=*/nullptr));
|
||||
auto* range_del_iter = m->NewRangeTombstoneIterator(
|
||||
ro, kMaxSequenceNumber, true /* immutable_memtable */);
|
||||
if (range_del_iter != nullptr) {
|
||||
@@ -1156,6 +1156,11 @@ void FlushJob::GetEffectiveCutoffUDTForPickedMemTables() {
|
||||
// Find the newest user-defined timestamps from all the flushed memtables.
|
||||
for (MemTable* m : mems_) {
|
||||
Slice table_newest_udt = m->GetNewestUDT();
|
||||
// Empty memtables can be legitimately created and flushed, for example
|
||||
// by error recovery flush attempts.
|
||||
if (table_newest_udt.empty()) {
|
||||
continue;
|
||||
}
|
||||
if (cutoff_udt_.empty() ||
|
||||
ucmp->CompareTimestamp(table_newest_udt, cutoff_udt_) > 0) {
|
||||
if (!cutoff_udt_.empty()) {
|
||||
|
||||
+11
-8
@@ -68,7 +68,8 @@ class FlushJobTestBase : public testing::Test {
|
||||
}
|
||||
|
||||
void NewDB() {
|
||||
ASSERT_OK(SetIdentityFile(WriteOptions(), env_, dbname_));
|
||||
ASSERT_OK(
|
||||
SetIdentityFile(WriteOptions(), env_, dbname_, Temperature::kUnknown));
|
||||
VersionEdit new_db;
|
||||
|
||||
new_db.SetLogNumber(0);
|
||||
@@ -114,7 +115,8 @@ class FlushJobTestBase : public testing::Test {
|
||||
}
|
||||
ASSERT_OK(s);
|
||||
// Make "CURRENT" file that points to the new manifest file.
|
||||
s = SetCurrentFile(WriteOptions(), fs_.get(), dbname_, 1, nullptr);
|
||||
s = SetCurrentFile(WriteOptions(), fs_.get(), dbname_, 1,
|
||||
Temperature::kUnknown, nullptr);
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
|
||||
@@ -140,12 +142,13 @@ class FlushJobTestBase : public testing::Test {
|
||||
column_families.emplace_back(cf_name, cf_options_);
|
||||
}
|
||||
|
||||
versions_.reset(new VersionSet(
|
||||
dbname_, &db_options_, env_options_, table_cache_.get(),
|
||||
&write_buffer_manager_, &write_controller_,
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
|
||||
/*db_id=*/"", /*db_session_id=*/"", /*daily_offpeak_time_utc=*/"",
|
||||
/*error_handler=*/nullptr, /*read_only=*/false));
|
||||
versions_.reset(
|
||||
new VersionSet(dbname_, &db_options_, env_options_, table_cache_.get(),
|
||||
&write_buffer_manager_, &write_controller_,
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
|
||||
test::kUnitTestDbId, /*db_session_id=*/"",
|
||||
/*daily_offpeak_time_utc=*/"",
|
||||
/*error_handler=*/nullptr, /*read_only=*/false));
|
||||
EXPECT_OK(versions_->Recover(column_families, false));
|
||||
}
|
||||
|
||||
|
||||
+14
-6
@@ -167,6 +167,10 @@ class ForwardLevelIterator : public InternalIterator {
|
||||
assert(valid_);
|
||||
return file_iter_->value();
|
||||
}
|
||||
uint64_t write_unix_time() const override {
|
||||
assert(valid_);
|
||||
return file_iter_->write_unix_time();
|
||||
}
|
||||
Status status() const override {
|
||||
if (!status_.ok()) {
|
||||
return status_;
|
||||
@@ -712,9 +716,11 @@ void ForwardIterator::RebuildIterators(bool refresh_sv) {
|
||||
UnownedPtr<const SeqnoToTimeMapping> seqno_to_time_mapping =
|
||||
sv_->GetSeqnoToTimeMapping();
|
||||
mutable_iter_ =
|
||||
sv_->mem->NewIterator(read_options_, seqno_to_time_mapping, &arena_);
|
||||
sv_->imm->AddIterators(read_options_, seqno_to_time_mapping, &imm_iters_,
|
||||
&arena_);
|
||||
sv_->mem->NewIterator(read_options_, seqno_to_time_mapping, &arena_,
|
||||
sv_->mutable_cf_options.prefix_extractor.get());
|
||||
sv_->imm->AddIterators(read_options_, seqno_to_time_mapping,
|
||||
sv_->mutable_cf_options.prefix_extractor.get(),
|
||||
&imm_iters_, &arena_);
|
||||
if (!read_options_.ignore_range_deletions) {
|
||||
std::unique_ptr<FragmentedRangeTombstoneIterator> range_del_iter(
|
||||
sv_->mem->NewRangeTombstoneIterator(
|
||||
@@ -781,9 +787,11 @@ void ForwardIterator::RenewIterators() {
|
||||
UnownedPtr<const SeqnoToTimeMapping> seqno_to_time_mapping =
|
||||
svnew->GetSeqnoToTimeMapping();
|
||||
mutable_iter_ =
|
||||
svnew->mem->NewIterator(read_options_, seqno_to_time_mapping, &arena_);
|
||||
svnew->imm->AddIterators(read_options_, seqno_to_time_mapping, &imm_iters_,
|
||||
&arena_);
|
||||
svnew->mem->NewIterator(read_options_, seqno_to_time_mapping, &arena_,
|
||||
svnew->mutable_cf_options.prefix_extractor.get());
|
||||
svnew->imm->AddIterators(read_options_, seqno_to_time_mapping,
|
||||
svnew->mutable_cf_options.prefix_extractor.get(),
|
||||
&imm_iters_, &arena_);
|
||||
ReadRangeDelAggregator range_del_agg(&cfd_->internal_comparator(),
|
||||
kMaxSequenceNumber /* upper_bound */);
|
||||
if (!read_options_.ignore_range_deletions) {
|
||||
|
||||
@@ -202,6 +202,10 @@ struct JobContext {
|
||||
// that corresponds to the set of files in 'live'.
|
||||
uint64_t manifest_file_number;
|
||||
uint64_t pending_manifest_file_number;
|
||||
|
||||
// Used for remote compaction. To prevent OPTIONS files from getting
|
||||
// purged by PurgeObsoleteFiles() of the primary host
|
||||
uint64_t min_options_file_number;
|
||||
uint64_t log_number;
|
||||
uint64_t prev_log_number;
|
||||
|
||||
|
||||
+24
-14
@@ -365,9 +365,12 @@ const char* EncodeKey(std::string* scratch, const Slice& target) {
|
||||
|
||||
class MemTableIterator : public InternalIterator {
|
||||
public:
|
||||
MemTableIterator(const MemTable& mem, const ReadOptions& read_options,
|
||||
UnownedPtr<const SeqnoToTimeMapping> seqno_to_time_mapping,
|
||||
Arena* arena, bool use_range_del_table = false)
|
||||
enum Kind { kPointEntries, kRangeDelEntries };
|
||||
MemTableIterator(
|
||||
Kind kind, const MemTable& mem, const ReadOptions& read_options,
|
||||
UnownedPtr<const SeqnoToTimeMapping> seqno_to_time_mapping = nullptr,
|
||||
Arena* arena = nullptr,
|
||||
const SliceTransform* cf_prefix_extractor = nullptr)
|
||||
: bloom_(nullptr),
|
||||
prefix_extractor_(mem.prefix_extractor_),
|
||||
comparator_(mem.comparator_),
|
||||
@@ -382,14 +385,21 @@ class MemTableIterator : public InternalIterator {
|
||||
arena_mode_(arena != nullptr),
|
||||
paranoid_memory_checks_(mem.moptions_.paranoid_memory_checks),
|
||||
allow_data_in_error(mem.moptions_.allow_data_in_errors) {
|
||||
if (use_range_del_table) {
|
||||
if (kind == kRangeDelEntries) {
|
||||
iter_ = mem.range_del_table_->GetIterator(arena);
|
||||
} else if (prefix_extractor_ != nullptr && !read_options.total_order_seek &&
|
||||
!read_options.auto_prefix_mode) {
|
||||
} else if (prefix_extractor_ != nullptr &&
|
||||
// NOTE: checking extractor equivalence when not pointer
|
||||
// equivalent is arguably too expensive for memtable
|
||||
prefix_extractor_ == cf_prefix_extractor &&
|
||||
(read_options.prefix_same_as_start ||
|
||||
(!read_options.total_order_seek &&
|
||||
!read_options.auto_prefix_mode))) {
|
||||
// Auto prefix mode is not implemented in memtable yet.
|
||||
assert(kind == kPointEntries);
|
||||
bloom_ = mem.bloom_filter_.get();
|
||||
iter_ = mem.table_->GetDynamicPrefixIterator(arena);
|
||||
} else {
|
||||
assert(kind == kPointEntries);
|
||||
iter_ = mem.table_->GetIterator(arena);
|
||||
}
|
||||
status_.PermitUncheckedError();
|
||||
@@ -433,8 +443,8 @@ class MemTableIterator : public InternalIterator {
|
||||
// iterator should only use prefix bloom filter
|
||||
Slice user_k_without_ts(ExtractUserKeyAndStripTimestamp(k, ts_sz_));
|
||||
if (prefix_extractor_->InDomain(user_k_without_ts)) {
|
||||
if (!bloom_->MayContain(
|
||||
prefix_extractor_->Transform(user_k_without_ts))) {
|
||||
Slice prefix = prefix_extractor_->Transform(user_k_without_ts);
|
||||
if (!bloom_->MayContain(prefix)) {
|
||||
PERF_COUNTER_ADD(bloom_memtable_miss_count, 1);
|
||||
valid_ = false;
|
||||
return;
|
||||
@@ -594,11 +604,13 @@ class MemTableIterator : public InternalIterator {
|
||||
|
||||
InternalIterator* MemTable::NewIterator(
|
||||
const ReadOptions& read_options,
|
||||
UnownedPtr<const SeqnoToTimeMapping> seqno_to_time_mapping, Arena* arena) {
|
||||
UnownedPtr<const SeqnoToTimeMapping> seqno_to_time_mapping, Arena* arena,
|
||||
const SliceTransform* prefix_extractor) {
|
||||
assert(arena != nullptr);
|
||||
auto mem = arena->AllocateAligned(sizeof(MemTableIterator));
|
||||
return new (mem)
|
||||
MemTableIterator(*this, read_options, seqno_to_time_mapping, arena);
|
||||
MemTableIterator(MemTableIterator::kPointEntries, *this, read_options,
|
||||
seqno_to_time_mapping, arena, prefix_extractor);
|
||||
}
|
||||
|
||||
FragmentedRangeTombstoneIterator* MemTable::NewRangeTombstoneIterator(
|
||||
@@ -633,8 +645,7 @@ FragmentedRangeTombstoneIterator* MemTable::NewRangeTombstoneIteratorInternal(
|
||||
cache->reader_mutex.lock();
|
||||
if (!cache->tombstones) {
|
||||
auto* unfragmented_iter = new MemTableIterator(
|
||||
*this, read_options, nullptr /* seqno_to_time_mapping= */,
|
||||
nullptr /* arena */, true /* use_range_del_table */);
|
||||
MemTableIterator::kRangeDelEntries, *this, read_options);
|
||||
cache->tombstones.reset(new FragmentedRangeTombstoneList(
|
||||
std::unique_ptr<InternalIterator>(unfragmented_iter),
|
||||
comparator_.comparator));
|
||||
@@ -655,8 +666,7 @@ void MemTable::ConstructFragmentedRangeTombstones() {
|
||||
if (!is_range_del_table_empty_.load(std::memory_order_relaxed)) {
|
||||
// TODO: plumb Env::IOActivity, Env::IOPriority
|
||||
auto* unfragmented_iter = new MemTableIterator(
|
||||
*this, ReadOptions(), nullptr /*seqno_to_time_mapping=*/,
|
||||
nullptr /* arena */, true /* use_range_del_table */);
|
||||
MemTableIterator::kRangeDelEntries, *this, ReadOptions());
|
||||
|
||||
fragmented_range_tombstone_list_ =
|
||||
std::make_unique<FragmentedRangeTombstoneList>(
|
||||
|
||||
+2
-1
@@ -210,7 +210,8 @@ class MemTable {
|
||||
// data, currently only needed for iterators serving user reads.
|
||||
InternalIterator* NewIterator(
|
||||
const ReadOptions& read_options,
|
||||
UnownedPtr<const SeqnoToTimeMapping> seqno_to_time_mapping, Arena* arena);
|
||||
UnownedPtr<const SeqnoToTimeMapping> seqno_to_time_mapping, Arena* arena,
|
||||
const SliceTransform* prefix_extractor);
|
||||
|
||||
// Returns an iterator that yields the range tombstones of the memtable.
|
||||
// The caller must ensure that the underlying MemTable remains live
|
||||
|
||||
+61
-33
@@ -214,20 +214,23 @@ Status MemTableListVersion::AddRangeTombstoneIterators(
|
||||
void MemTableListVersion::AddIterators(
|
||||
const ReadOptions& options,
|
||||
UnownedPtr<const SeqnoToTimeMapping> seqno_to_time_mapping,
|
||||
const SliceTransform* prefix_extractor,
|
||||
std::vector<InternalIterator*>* iterator_list, Arena* arena) {
|
||||
for (auto& m : memlist_) {
|
||||
iterator_list->push_back(
|
||||
m->NewIterator(options, seqno_to_time_mapping, arena));
|
||||
iterator_list->push_back(m->NewIterator(options, seqno_to_time_mapping,
|
||||
arena, prefix_extractor));
|
||||
}
|
||||
}
|
||||
|
||||
void MemTableListVersion::AddIterators(
|
||||
const ReadOptions& options,
|
||||
UnownedPtr<const SeqnoToTimeMapping> seqno_to_time_mapping,
|
||||
const SliceTransform* prefix_extractor,
|
||||
MergeIteratorBuilder* merge_iter_builder, bool add_range_tombstone_iter) {
|
||||
for (auto& m : memlist_) {
|
||||
auto mem_iter = m->NewIterator(options, seqno_to_time_mapping,
|
||||
merge_iter_builder->GetArena());
|
||||
auto mem_iter =
|
||||
m->NewIterator(options, seqno_to_time_mapping,
|
||||
merge_iter_builder->GetArena(), prefix_extractor);
|
||||
if (!add_range_tombstone_iter || options.ignore_range_deletions) {
|
||||
merge_iter_builder->AddIterator(mem_iter);
|
||||
} else {
|
||||
@@ -582,37 +585,28 @@ Status MemTableList::TryInstallMemtableFlushResults(
|
||||
|
||||
// TODO(myabandeh): Not sure how batch_count could be 0 here.
|
||||
if (batch_count > 0) {
|
||||
uint64_t min_wal_number_to_keep = 0;
|
||||
assert(edit_list.size() > 0);
|
||||
if (vset->db_options()->allow_2pc) {
|
||||
// Note that if mempurge is successful, the edit_list will
|
||||
// not be applicable (contains info of new min_log number to keep,
|
||||
// and level 0 file path of SST file created during normal flush,
|
||||
// so both pieces of information are irrelevant after a successful
|
||||
// mempurge operation).
|
||||
min_wal_number_to_keep = PrecomputeMinLogNumberToKeep2PC(
|
||||
vset, *cfd, edit_list, memtables_to_flush, prep_tracker);
|
||||
|
||||
// We piggyback the information of earliest log file to keep in the
|
||||
// manifest entry for the last file flushed.
|
||||
VersionEdit edit;
|
||||
#ifdef ROCKSDB_ASSERT_STATUS_CHECKED
|
||||
if (memtables_to_flush.size() == memlist.size()) {
|
||||
// TODO(yuzhangyu): remove this testing code once the
|
||||
// `GetEditForDroppingCurrentVersion` API is used by the atomic data
|
||||
// replacement. This function can get the same edits for wal related
|
||||
// fields, and some duplicated fields as contained already in edit_list
|
||||
// for column family's recovery.
|
||||
edit = GetEditForDroppingCurrentVersion(cfd, vset, prep_tracker);
|
||||
} else {
|
||||
min_wal_number_to_keep =
|
||||
PrecomputeMinLogNumberToKeepNon2PC(vset, *cfd, edit_list);
|
||||
edit = GetDBRecoveryEditForObsoletingMemTables(
|
||||
vset, *cfd, edit_list, memtables_to_flush, prep_tracker);
|
||||
}
|
||||
|
||||
VersionEdit wal_deletion;
|
||||
wal_deletion.SetMinLogNumberToKeep(min_wal_number_to_keep);
|
||||
if (vset->db_options()->track_and_verify_wals_in_manifest) {
|
||||
if (min_wal_number_to_keep >
|
||||
vset->GetWalSet().GetMinWalNumberToKeep()) {
|
||||
wal_deletion.DeleteWalsBefore(min_wal_number_to_keep);
|
||||
}
|
||||
TEST_SYNC_POINT_CALLBACK(
|
||||
"MemTableList::TryInstallMemtableFlushResults:"
|
||||
"AfterComputeMinWalToKeep",
|
||||
nullptr);
|
||||
}
|
||||
edit_list.push_back(&wal_deletion);
|
||||
#else
|
||||
edit = GetDBRecoveryEditForObsoletingMemTables(
|
||||
vset, *cfd, edit_list, memtables_to_flush, prep_tracker);
|
||||
#endif // ROCKSDB_ASSERT_STATUS_CHECKED
|
||||
TEST_SYNC_POINT_CALLBACK(
|
||||
"MemTableList::TryInstallMemtableFlushResults:"
|
||||
"AfterComputeMinWalToKeep",
|
||||
nullptr);
|
||||
edit_list.push_back(&edit);
|
||||
|
||||
const auto manifest_write_cb = [this, cfd, batch_count, log_buffer,
|
||||
to_delete, mu](const Status& status) {
|
||||
@@ -1026,4 +1020,38 @@ void MemTableList::RemoveOldMemTables(uint64_t log_number,
|
||||
ResetTrimHistoryNeeded();
|
||||
}
|
||||
|
||||
VersionEdit MemTableList::GetEditForDroppingCurrentVersion(
|
||||
const ColumnFamilyData* cfd, VersionSet* vset,
|
||||
LogsWithPrepTracker* prep_tracker) const {
|
||||
assert(cfd);
|
||||
auto& memlist = current_->memlist_;
|
||||
if (memlist.empty()) {
|
||||
return VersionEdit();
|
||||
}
|
||||
|
||||
uint64_t max_next_log_number = 0;
|
||||
autovector<VersionEdit*> edit_list;
|
||||
autovector<MemTable*> memtables_to_drop;
|
||||
for (auto it = memlist.rbegin(); it != memlist.rend(); ++it) {
|
||||
MemTable* m = *it;
|
||||
memtables_to_drop.push_back(m);
|
||||
max_next_log_number = std::max(m->GetNextLogNumber(), max_next_log_number);
|
||||
}
|
||||
|
||||
// Check the obsoleted MemTables' impact on WALs related to DB's recovery (min
|
||||
// log number to keep, a delta of WAL files to delete).
|
||||
VersionEdit edit_with_log_number;
|
||||
edit_with_log_number.SetPrevLogNumber(0);
|
||||
edit_with_log_number.SetLogNumber(max_next_log_number);
|
||||
edit_list.push_back(&edit_with_log_number);
|
||||
VersionEdit edit = GetDBRecoveryEditForObsoletingMemTables(
|
||||
vset, *cfd, edit_list, memtables_to_drop, prep_tracker);
|
||||
|
||||
// Set fields related to the column family's recovery.
|
||||
edit.SetColumnFamily(cfd->GetID());
|
||||
edit.SetPrevLogNumber(0);
|
||||
edit.SetLogNumber(max_next_log_number);
|
||||
return edit;
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -113,11 +113,13 @@ class MemTableListVersion {
|
||||
|
||||
void AddIterators(const ReadOptions& options,
|
||||
UnownedPtr<const SeqnoToTimeMapping> seqno_to_time_mapping,
|
||||
const SliceTransform* prefix_extractor,
|
||||
std::vector<InternalIterator*>* iterator_list,
|
||||
Arena* arena);
|
||||
|
||||
void AddIterators(const ReadOptions& options,
|
||||
UnownedPtr<const SeqnoToTimeMapping> seqno_to_time_mapping,
|
||||
const SliceTransform* prefix_extractor,
|
||||
MergeIteratorBuilder* merge_iter_builder,
|
||||
bool add_range_tombstone_iter);
|
||||
|
||||
@@ -447,6 +449,12 @@ class MemTableList {
|
||||
void RemoveOldMemTables(uint64_t log_number,
|
||||
autovector<MemTable*>* to_delete);
|
||||
|
||||
// This API is only used by atomic date replacement. To get an edit for
|
||||
// dropping the current `MemTableListVersion`.
|
||||
VersionEdit GetEditForDroppingCurrentVersion(
|
||||
const ColumnFamilyData* cfd, VersionSet* vset,
|
||||
LogsWithPrepTracker* prep_tracker) const;
|
||||
|
||||
private:
|
||||
friend Status InstallMemtableAtomicFlushResults(
|
||||
const autovector<MemTableList*>* imm_lists,
|
||||
|
||||
+4
-2
@@ -443,7 +443,8 @@ class Repairer {
|
||||
ro.total_order_seek = true;
|
||||
Arena arena;
|
||||
ScopedArenaPtr<InternalIterator> iter(
|
||||
mem->NewIterator(ro, /*seqno_to_time_mapping=*/nullptr, &arena));
|
||||
mem->NewIterator(ro, /*seqno_to_time_mapping=*/nullptr, &arena,
|
||||
/*prefix_extractor=*/nullptr));
|
||||
int64_t _current_time = 0;
|
||||
immutable_db_options_.clock->GetCurrentTime(&_current_time)
|
||||
.PermitUncheckedError(); // ignore error
|
||||
@@ -451,7 +452,8 @@ class Repairer {
|
||||
meta.file_creation_time = current_time;
|
||||
SnapshotChecker* snapshot_checker = DisableGCSnapshotChecker::Instance();
|
||||
|
||||
auto write_hint = cfd->CalculateSSTWriteHint(0);
|
||||
auto write_hint =
|
||||
cfd->current()->storage_info()->CalculateSSTWriteHint(/*level=*/0);
|
||||
std::vector<std::unique_ptr<FragmentedRangeTombstoneIterator>>
|
||||
range_del_iters;
|
||||
auto range_del_iter = mem->NewRangeTombstoneIterator(
|
||||
|
||||
@@ -164,6 +164,7 @@ Status TableCache::GetTableReader(
|
||||
}
|
||||
|
||||
Cache::Handle* TableCache::Lookup(Cache* cache, uint64_t file_number) {
|
||||
// NOTE: sharing same Cache with BlobFileCache
|
||||
Slice key = GetSliceForFileNumber(&file_number);
|
||||
return cache->Lookup(key);
|
||||
}
|
||||
@@ -179,6 +180,7 @@ Status TableCache::FindTable(
|
||||
size_t max_file_size_for_l0_meta_pin, Temperature file_temperature) {
|
||||
PERF_TIMER_GUARD_WITH_CLOCK(find_table_nanos, ioptions_.clock);
|
||||
uint64_t number = file_meta.fd.GetNumber();
|
||||
// NOTE: sharing same Cache with BlobFileCache
|
||||
Slice key = GetSliceForFileNumber(&number);
|
||||
*handle = cache_.Lookup(key);
|
||||
TEST_SYNC_POINT_CALLBACK("TableCache::FindTable:0",
|
||||
|
||||
+14
-11
@@ -24,6 +24,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "cache/cache_reservation_manager.h"
|
||||
#include "db/blob/blob_file_cache.h"
|
||||
#include "db/blob/blob_file_meta.h"
|
||||
#include "db/dbformat.h"
|
||||
#include "db/internal_stats.h"
|
||||
@@ -269,9 +270,10 @@ class VersionBuilder::Rep {
|
||||
std::unordered_map<uint64_t, int> table_file_levels_;
|
||||
// Current compact cursors that should be changed after the last compaction
|
||||
std::unordered_map<int, InternalKey> updated_compact_cursors_;
|
||||
std::shared_ptr<NewestFirstByEpochNumber> level_zero_cmp_by_epochno_;
|
||||
std::shared_ptr<NewestFirstBySeqNo> level_zero_cmp_by_seqno_;
|
||||
std::shared_ptr<BySmallestKey> level_nonzero_cmp_;
|
||||
const std::shared_ptr<const NewestFirstByEpochNumber>
|
||||
level_zero_cmp_by_epochno_;
|
||||
const std::shared_ptr<const NewestFirstBySeqNo> level_zero_cmp_by_seqno_;
|
||||
const std::shared_ptr<const BySmallestKey> level_nonzero_cmp_;
|
||||
|
||||
// Mutable metadata objects for all blob files affected by the series of
|
||||
// version edits.
|
||||
@@ -743,12 +745,9 @@ class VersionBuilder::Rep {
|
||||
return Status::Corruption("VersionBuilder", oss.str());
|
||||
}
|
||||
|
||||
// Note: we use C++11 for now but in C++14, this could be done in a more
|
||||
// elegant way using generalized lambda capture.
|
||||
VersionSet* const vs = version_set_;
|
||||
const ImmutableCFOptions* const ioptions = ioptions_;
|
||||
|
||||
auto deleter = [vs, ioptions](SharedBlobFileMetaData* shared_meta) {
|
||||
auto deleter = [vs = version_set_, ioptions = ioptions_,
|
||||
bc = cfd_ ? cfd_->blob_file_cache()
|
||||
: nullptr](SharedBlobFileMetaData* shared_meta) {
|
||||
if (vs) {
|
||||
assert(ioptions);
|
||||
assert(!ioptions->cf_paths.empty());
|
||||
@@ -757,6 +756,9 @@ class VersionBuilder::Rep {
|
||||
vs->AddObsoleteBlobFile(shared_meta->GetBlobFileNumber(),
|
||||
ioptions->cf_paths.front().path);
|
||||
}
|
||||
if (bc) {
|
||||
bc->Evict(shared_meta->GetBlobFileNumber());
|
||||
}
|
||||
|
||||
delete shared_meta;
|
||||
};
|
||||
@@ -765,7 +767,7 @@ class VersionBuilder::Rep {
|
||||
blob_file_number, blob_file_addition.GetTotalBlobCount(),
|
||||
blob_file_addition.GetTotalBlobBytes(),
|
||||
blob_file_addition.GetChecksumMethod(),
|
||||
blob_file_addition.GetChecksumValue(), deleter);
|
||||
blob_file_addition.GetChecksumValue(), std::move(deleter));
|
||||
|
||||
mutable_blob_file_metas_.emplace(
|
||||
blob_file_number, MutableBlobFileMetaData(std::move(shared_meta)));
|
||||
@@ -1591,7 +1593,8 @@ class VersionBuilder::Rep {
|
||||
}
|
||||
auto iter = mutable_blob_file_metas_.find(missing_blob_file);
|
||||
assert(iter != mutable_blob_file_metas_.end());
|
||||
auto linked_ssts = iter->second.GetLinkedSsts();
|
||||
const std::unordered_set<uint64_t>& linked_ssts =
|
||||
iter->second.GetLinkedSsts();
|
||||
// TODO(yuzhangyu): In theory, if no L0 SST files ara missing, and only
|
||||
// blob files exclusively linked to a L0 suffix are missing, we can
|
||||
// recover to a valid point in time too. We don't recover that type of
|
||||
|
||||
@@ -100,7 +100,9 @@ using VersionBuilderUPtr = std::unique_ptr<BaseReferencedVersionBuilder>;
|
||||
// A class used for scanning MANIFEST file.
|
||||
// VersionEditHandler reads a MANIFEST file, parses the version edits, and
|
||||
// builds the version set's in-memory state, e.g. the version storage info for
|
||||
// the versions of column families.
|
||||
// the versions of column families. It replays all the version edits in one
|
||||
// MANIFEST file to build the end version.
|
||||
//
|
||||
// To use this class and its subclasses,
|
||||
// 1. Create an object of VersionEditHandler or its subclasses.
|
||||
// VersionEditHandler handler(read_only, column_families, version_set,
|
||||
@@ -240,11 +242,19 @@ class VersionEditHandler : public VersionEditHandlerBase {
|
||||
};
|
||||
|
||||
// A class similar to its base class, i.e. VersionEditHandler.
|
||||
// VersionEditHandlerPointInTime restores the versions to the most recent point
|
||||
// in time such that at this point, the version does not have missing files. Or
|
||||
// if `allow_incomplete_valid_version` is true, only a suffix of L0 files (and
|
||||
// Unlike VersionEditHandler that only aims to build the end version, this class
|
||||
// supports building the most recent point in time version. A point in time
|
||||
// version is a version for which no files are missing, or if
|
||||
// `allow_incomplete_valid_version` is true, only a suffix of L0 files (and
|
||||
// their associated blob files) are missing.
|
||||
//
|
||||
// Building a point in time version when end version is not available can
|
||||
// be useful for best efforts recovery (options.best_efforts_recovery), which
|
||||
// uses this class and sets `allow_incomplete_valid_version` to true.
|
||||
// It's also useful for secondary instances/follower instances for which end
|
||||
// version could be transiently unavailable. These two cases use subclass
|
||||
// `ManifestTailer` and sets `allow_incomplete_valid_version` to false.
|
||||
//
|
||||
// Not thread-safe, external synchronization is necessary if an object of
|
||||
// VersionEditHandlerPointInTime is shared by multiple threads.
|
||||
class VersionEditHandlerPointInTime : public VersionEditHandler {
|
||||
@@ -310,6 +320,12 @@ class VersionEditHandlerPointInTime : public VersionEditHandler {
|
||||
void AtomicUpdateVersionsApply();
|
||||
};
|
||||
|
||||
// A class similar to `VersionEditHandlerPointInTime` that parse MANIFEST and
|
||||
// builds point in time version.
|
||||
// `ManifestTailer` supports reading one MANIFEST file in multiple tailing
|
||||
// attempts and supports switching to a different MANIFEST after
|
||||
// `PrepareToReadNewManifest` is called. This class is used by secondary and
|
||||
// follower instance.
|
||||
class ManifestTailer : public VersionEditHandlerPointInTime {
|
||||
public:
|
||||
explicit ManifestTailer(std::vector<ColumnFamilyDescriptor> column_families,
|
||||
|
||||
+47
-33
@@ -44,8 +44,8 @@
|
||||
#include "table/compaction_merging_iterator.h"
|
||||
|
||||
#if USE_COROUTINES
|
||||
#include "folly/experimental/coro/BlockingWait.h"
|
||||
#include "folly/experimental/coro/Collect.h"
|
||||
#include "folly/coro/BlockingWait.h"
|
||||
#include "folly/coro/Collect.h"
|
||||
#endif
|
||||
#include "file/filename.h"
|
||||
#include "file/random_access_file_reader.h"
|
||||
@@ -1050,6 +1050,11 @@ class LevelIterator final : public InternalIterator {
|
||||
return file_iter_.value();
|
||||
}
|
||||
|
||||
uint64_t write_unix_time() const override {
|
||||
assert(Valid());
|
||||
return file_iter_.write_unix_time();
|
||||
}
|
||||
|
||||
Status status() const override {
|
||||
return file_iter_.iter() ? file_iter_.status() : Status::OK();
|
||||
}
|
||||
@@ -3772,14 +3777,17 @@ void VersionStorageInfo::ComputeFilesMarkedForForcedBlobGC(
|
||||
return;
|
||||
}
|
||||
|
||||
// Compute the sum of total and garbage bytes over the oldest batch of blob
|
||||
// files. The oldest batch is defined as the set of blob files which are
|
||||
// kept alive by the same SSTs as the very oldest one. Here is a toy example.
|
||||
// Let's assume we have three SSTs 1, 2, and 3, and four blob files 10, 11,
|
||||
// 12, and 13. Also, let's say SSTs 1 and 2 both rely on blob file 10 and
|
||||
// potentially some higher-numbered ones, while SST 3 relies on blob file 12
|
||||
// and potentially some higher-numbered ones. Then, the SST to oldest blob
|
||||
// file mapping is as follows:
|
||||
// Compute the sum of total and garbage bytes over the batch of blob files
|
||||
// currently eligible for garbage collection based on
|
||||
// blob_garbage_collection_age_cutoff, and if the garbage ratio exceeds
|
||||
// blob_garbage_collection_force_threshold, schedule compaction for the
|
||||
// SST files that reference the oldest batch of blob files. Here is a toy
|
||||
// example. Let's assume we have three SSTs 1, 2, and 3, and four blob files
|
||||
// 10, 11, 12, and 13, which correspond to the range that is eligible for GC
|
||||
// and satisfy the garbage ratio threshold. Also, let's say SSTs 1 and 2 both
|
||||
// rely on blob file 10 and potentially some higher-numbered ones, while SST 3
|
||||
// relies on blob file 12 and potentially some higher-numbered ones. Then, the
|
||||
// SST to oldest blob file mapping is as follows:
|
||||
//
|
||||
// SST file number Oldest blob file number
|
||||
// 1 10
|
||||
@@ -3797,11 +3805,6 @@ void VersionStorageInfo::ComputeFilesMarkedForForcedBlobGC(
|
||||
//
|
||||
// Then, the oldest batch of blob files consists of blob files 10 and 11,
|
||||
// and we can get rid of them by forcing the compaction of SSTs 1 and 2.
|
||||
//
|
||||
// Note that the overall ratio of garbage computed for the batch has to exceed
|
||||
// blob_garbage_collection_force_threshold and the entire batch has to be
|
||||
// eligible for GC according to blob_garbage_collection_age_cutoff in order
|
||||
// for us to schedule any compactions.
|
||||
const auto& oldest_meta = blob_files_.front();
|
||||
assert(oldest_meta);
|
||||
|
||||
@@ -3818,25 +3821,10 @@ void VersionStorageInfo::ComputeFilesMarkedForForcedBlobGC(
|
||||
const auto& meta = blob_files_[count];
|
||||
assert(meta);
|
||||
|
||||
if (!meta->GetLinkedSsts().empty()) {
|
||||
// Found the beginning of the next batch of blob files
|
||||
break;
|
||||
}
|
||||
|
||||
sum_total_blob_bytes += meta->GetTotalBlobBytes();
|
||||
sum_garbage_blob_bytes += meta->GetGarbageBlobBytes();
|
||||
}
|
||||
|
||||
if (count < blob_files_.size()) {
|
||||
const auto& meta = blob_files_[count];
|
||||
assert(meta);
|
||||
|
||||
if (meta->GetLinkedSsts().empty()) {
|
||||
// Some files in the oldest batch are not eligible for GC
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (sum_garbage_blob_bytes <
|
||||
blob_garbage_collection_force_threshold * sum_total_blob_bytes) {
|
||||
return;
|
||||
@@ -4919,6 +4907,27 @@ bool VersionStorageInfo::RangeMightExistAfterSortedRun(
|
||||
return false;
|
||||
}
|
||||
|
||||
Env::WriteLifeTimeHint VersionStorageInfo::CalculateSSTWriteHint(
|
||||
int level) const {
|
||||
if (compaction_style_ != kCompactionStyleLevel) {
|
||||
return Env::WLTH_NOT_SET;
|
||||
}
|
||||
if (level == 0) {
|
||||
return Env::WLTH_MEDIUM;
|
||||
}
|
||||
|
||||
// L1: medium, L2: long, ...
|
||||
if (level - base_level_ >= 2) {
|
||||
return Env::WLTH_EXTREME;
|
||||
} else if (level < base_level_) {
|
||||
// There is no restriction which prevents level passed in to be smaller
|
||||
// than base_level.
|
||||
return Env::WLTH_MEDIUM;
|
||||
}
|
||||
return static_cast<Env::WriteLifeTimeHint>(
|
||||
level - base_level_ + static_cast<int>(Env::WLTH_MEDIUM));
|
||||
}
|
||||
|
||||
void Version::AddLiveFiles(std::vector<uint64_t>* live_table_files,
|
||||
std::vector<uint64_t>* live_blob_files) const {
|
||||
assert(live_table_files);
|
||||
@@ -5122,6 +5131,7 @@ VersionSet::VersionSet(
|
||||
fs_(_db_options->fs, io_tracer),
|
||||
clock_(_db_options->clock),
|
||||
dbname_(dbname),
|
||||
db_id_(db_id),
|
||||
db_options_(_db_options),
|
||||
next_file_number_(2),
|
||||
manifest_file_number_(0), // Filled by Recover()
|
||||
@@ -5511,6 +5521,10 @@ Status VersionSet::ProcessManifestWrites(
|
||||
std::unique_ptr<log::Writer> new_desc_log_ptr;
|
||||
{
|
||||
FileOptions opt_file_opts = fs_->OptimizeForManifestWrite(file_options_);
|
||||
// DB option (in file_options_) takes precedence when not kUnknown
|
||||
if (file_options_.temperature != Temperature::kUnknown) {
|
||||
opt_file_opts.temperature = file_options_.temperature;
|
||||
}
|
||||
mu->Unlock();
|
||||
TEST_SYNC_POINT("VersionSet::LogAndApply:WriteManifestStart");
|
||||
TEST_SYNC_POINT_CALLBACK("VersionSet::LogAndApply:WriteManifest", nullptr);
|
||||
@@ -5637,9 +5651,9 @@ Status VersionSet::ProcessManifestWrites(
|
||||
assert(manifest_io_status.ok());
|
||||
}
|
||||
if (s.ok() && new_descriptor_log) {
|
||||
io_s = SetCurrentFile(write_options, fs_.get(), dbname_,
|
||||
pending_manifest_file_number_,
|
||||
dir_contains_current_file);
|
||||
io_s = SetCurrentFile(
|
||||
write_options, fs_.get(), dbname_, pending_manifest_file_number_,
|
||||
file_options_.temperature, dir_contains_current_file);
|
||||
if (!io_s.ok()) {
|
||||
s = io_s;
|
||||
// Quarantine old manifest file in case new manifest file's CURRENT file
|
||||
|
||||
+13
-3
@@ -49,8 +49,8 @@
|
||||
#include "db/write_controller.h"
|
||||
#include "env/file_system_tracer.h"
|
||||
#if USE_COROUTINES
|
||||
#include "folly/experimental/coro/BlockingWait.h"
|
||||
#include "folly/experimental/coro/Collect.h"
|
||||
#include "folly/coro/BlockingWait.h"
|
||||
#include "folly/coro/Collect.h"
|
||||
#endif
|
||||
#include "monitoring/instrumented_mutex.h"
|
||||
#include "options/db_options.h"
|
||||
@@ -626,6 +626,8 @@ class VersionStorageInfo {
|
||||
const Slice& largest_user_key,
|
||||
int last_level, int last_l0_idx);
|
||||
|
||||
Env::WriteLifeTimeHint CalculateSSTWriteHint(int level) const;
|
||||
|
||||
private:
|
||||
void ComputeCompensatedSizes();
|
||||
void UpdateNumNonEmptyLevels();
|
||||
@@ -1277,6 +1279,15 @@ class VersionSet {
|
||||
bool no_error_if_files_missing = false, bool is_retry = false,
|
||||
Status* log_status = nullptr);
|
||||
|
||||
// Do a best-efforts recovery (Options.best_efforts_recovery=true) from all
|
||||
// available MANIFEST files. Similar to `Recover` with these differences:
|
||||
// 1) not only the latest MANIFEST can be used, if it's not available or
|
||||
// no successful recovery can be achieved with it, this function also tries
|
||||
// to recover from previous MANIFEST files, in reverse chronological order
|
||||
// until a successful recovery can be achieved.
|
||||
// 2) this function doesn't just aim to recover to the latest version, if that
|
||||
// is not available, the most recent point in time version will be saved in
|
||||
// memory. Check doc for `VersionEditHandlerPointInTime` for more details.
|
||||
Status TryRecover(const std::vector<ColumnFamilyDescriptor>& column_families,
|
||||
bool read_only,
|
||||
const std::vector<std::string>& files_in_dbname,
|
||||
@@ -1503,7 +1514,6 @@ class VersionSet {
|
||||
void GetLiveFilesMetaData(std::vector<LiveFileMetaData>* metadata);
|
||||
|
||||
void AddObsoleteBlobFile(uint64_t blob_file_number, std::string path) {
|
||||
// TODO: Erase file from BlobFileCache?
|
||||
obsolete_blob_files_.emplace_back(blob_file_number, std::move(path));
|
||||
}
|
||||
|
||||
|
||||
+50
-128
@@ -727,20 +727,7 @@ TEST_F(VersionStorageInfoTest, ForcedBlobGCSingleBatch) {
|
||||
ASSERT_TRUE(vstorage_.FilesMarkedForForcedBlobGC().empty());
|
||||
}
|
||||
|
||||
// Part of the oldest batch of blob files (specifically, #12 and #13) is
|
||||
// ineligible for GC due to the age cutoff
|
||||
|
||||
{
|
||||
constexpr double age_cutoff = 0.5;
|
||||
constexpr double force_threshold = 0.0;
|
||||
vstorage_.ComputeFilesMarkedForForcedBlobGC(
|
||||
age_cutoff, force_threshold, /*enable_blob_garbage_collection=*/true);
|
||||
|
||||
ASSERT_TRUE(vstorage_.FilesMarkedForForcedBlobGC().empty());
|
||||
}
|
||||
|
||||
// Oldest batch is eligible based on age cutoff but its overall garbage ratio
|
||||
// is below threshold
|
||||
// Overall garbage ratio of eligible files is below threshold
|
||||
|
||||
{
|
||||
constexpr double age_cutoff = 1.0;
|
||||
@@ -751,8 +738,7 @@ TEST_F(VersionStorageInfoTest, ForcedBlobGCSingleBatch) {
|
||||
ASSERT_TRUE(vstorage_.FilesMarkedForForcedBlobGC().empty());
|
||||
}
|
||||
|
||||
// Oldest batch is eligible based on age cutoff and its overall garbage ratio
|
||||
// meets threshold
|
||||
// Overall garbage ratio of eligible files meets threshold
|
||||
|
||||
{
|
||||
constexpr double age_cutoff = 1.0;
|
||||
@@ -878,20 +864,7 @@ TEST_F(VersionStorageInfoTest, ForcedBlobGCMultipleBatches) {
|
||||
ASSERT_TRUE(vstorage_.FilesMarkedForForcedBlobGC().empty());
|
||||
}
|
||||
|
||||
// Part of the oldest batch of blob files (specifically, the second file) is
|
||||
// ineligible for GC due to the age cutoff
|
||||
|
||||
{
|
||||
constexpr double age_cutoff = 0.25;
|
||||
constexpr double force_threshold = 0.0;
|
||||
vstorage_.ComputeFilesMarkedForForcedBlobGC(
|
||||
age_cutoff, force_threshold, /*enable_blob_garbage_collection=*/true);
|
||||
|
||||
ASSERT_TRUE(vstorage_.FilesMarkedForForcedBlobGC().empty());
|
||||
}
|
||||
|
||||
// Oldest batch is eligible based on age cutoff but its overall garbage ratio
|
||||
// is below threshold
|
||||
// Overall garbage ratio of eligible files is below threshold
|
||||
|
||||
{
|
||||
constexpr double age_cutoff = 0.5;
|
||||
@@ -902,8 +875,7 @@ TEST_F(VersionStorageInfoTest, ForcedBlobGCMultipleBatches) {
|
||||
ASSERT_TRUE(vstorage_.FilesMarkedForForcedBlobGC().empty());
|
||||
}
|
||||
|
||||
// Oldest batch is eligible based on age cutoff and its overall garbage ratio
|
||||
// meets threshold
|
||||
// Overall garbage ratio of eligible files meets threshold
|
||||
|
||||
{
|
||||
constexpr double age_cutoff = 0.5;
|
||||
@@ -929,48 +901,6 @@ TEST_F(VersionStorageInfoTest, ForcedBlobGCMultipleBatches) {
|
||||
ASSERT_EQ(ssts_to_be_compacted[0], expected_ssts_to_be_compacted[0]);
|
||||
ASSERT_EQ(ssts_to_be_compacted[1], expected_ssts_to_be_compacted[1]);
|
||||
}
|
||||
|
||||
// Now try the last two cases again with a greater than necessary age cutoff
|
||||
|
||||
// Oldest batch is eligible based on age cutoff but its overall garbage ratio
|
||||
// is below threshold
|
||||
|
||||
{
|
||||
constexpr double age_cutoff = 0.75;
|
||||
constexpr double force_threshold = 0.6;
|
||||
vstorage_.ComputeFilesMarkedForForcedBlobGC(
|
||||
age_cutoff, force_threshold, /*enable_blob_garbage_collection=*/true);
|
||||
|
||||
ASSERT_TRUE(vstorage_.FilesMarkedForForcedBlobGC().empty());
|
||||
}
|
||||
|
||||
// Oldest batch is eligible based on age cutoff and its overall garbage ratio
|
||||
// meets threshold
|
||||
|
||||
{
|
||||
constexpr double age_cutoff = 0.75;
|
||||
constexpr double force_threshold = 0.5;
|
||||
vstorage_.ComputeFilesMarkedForForcedBlobGC(
|
||||
age_cutoff, force_threshold, /*enable_blob_garbage_collection=*/true);
|
||||
|
||||
auto ssts_to_be_compacted = vstorage_.FilesMarkedForForcedBlobGC();
|
||||
ASSERT_EQ(ssts_to_be_compacted.size(), 2);
|
||||
|
||||
std::sort(ssts_to_be_compacted.begin(), ssts_to_be_compacted.end(),
|
||||
[](const std::pair<int, FileMetaData*>& lhs,
|
||||
const std::pair<int, FileMetaData*>& rhs) {
|
||||
assert(lhs.second);
|
||||
assert(rhs.second);
|
||||
return lhs.second->fd.GetNumber() < rhs.second->fd.GetNumber();
|
||||
});
|
||||
|
||||
const autovector<std::pair<int, FileMetaData*>>
|
||||
expected_ssts_to_be_compacted{{level, level_files[0]},
|
||||
{level, level_files[1]}};
|
||||
|
||||
ASSERT_EQ(ssts_to_be_compacted[0], expected_ssts_to_be_compacted[0]);
|
||||
ASSERT_EQ(ssts_to_be_compacted[1], expected_ssts_to_be_compacted[1]);
|
||||
}
|
||||
}
|
||||
|
||||
class VersionStorageInfoTimestampTest : public VersionStorageInfoTestBase {
|
||||
@@ -1282,6 +1212,8 @@ class VersionSetTestBase {
|
||||
assert(column_families != nullptr);
|
||||
assert(last_seqno != nullptr);
|
||||
assert(log_writer != nullptr);
|
||||
ASSERT_OK(
|
||||
SetIdentityFile(WriteOptions(), env_, dbname_, Temperature::kUnknown));
|
||||
VersionEdit new_db;
|
||||
if (db_options_.write_dbid_to_manifest) {
|
||||
DBOptions tmp_db_options;
|
||||
@@ -1415,16 +1347,20 @@ class VersionSetTestBase {
|
||||
}
|
||||
}
|
||||
|
||||
void CreateCurrentFile() {
|
||||
// Make "CURRENT" file point to the new manifest file.
|
||||
ASSERT_OK(SetCurrentFile(WriteOptions(), fs_.get(), dbname_, 1,
|
||||
Temperature::kUnknown,
|
||||
/* dir_contains_current_file */ nullptr));
|
||||
}
|
||||
|
||||
// Create DB with 3 column families.
|
||||
void NewDB() {
|
||||
SequenceNumber last_seqno;
|
||||
std::unique_ptr<log::Writer> log_writer;
|
||||
ASSERT_OK(SetIdentityFile(WriteOptions(), env_, dbname_));
|
||||
PrepareManifest(&column_families_, &last_seqno, &log_writer);
|
||||
log_writer.reset();
|
||||
// Make "CURRENT" file point to the new manifest file.
|
||||
Status s = SetCurrentFile(WriteOptions(), fs_.get(), dbname_, 1, nullptr);
|
||||
ASSERT_OK(s);
|
||||
CreateCurrentFile();
|
||||
|
||||
EXPECT_OK(versions_->Recover(column_families_, false));
|
||||
EXPECT_EQ(column_families_.size(),
|
||||
@@ -2600,7 +2536,7 @@ class VersionSetAtomicGroupTest : public VersionSetTestBase,
|
||||
edits_[i].MarkAtomicGroup(--remaining);
|
||||
edits_[i].SetLastSequence(last_seqno_++);
|
||||
}
|
||||
ASSERT_OK(SetCurrentFile(WriteOptions(), fs_.get(), dbname_, 1, nullptr));
|
||||
CreateCurrentFile();
|
||||
}
|
||||
|
||||
void SetupIncompleteTrailingAtomicGroup(int atomic_group_size) {
|
||||
@@ -2612,7 +2548,7 @@ class VersionSetAtomicGroupTest : public VersionSetTestBase,
|
||||
edits_[i].MarkAtomicGroup(--remaining);
|
||||
edits_[i].SetLastSequence(last_seqno_++);
|
||||
}
|
||||
ASSERT_OK(SetCurrentFile(WriteOptions(), fs_.get(), dbname_, 1, nullptr));
|
||||
CreateCurrentFile();
|
||||
}
|
||||
|
||||
void SetupCorruptedAtomicGroup(int atomic_group_size) {
|
||||
@@ -2626,7 +2562,7 @@ class VersionSetAtomicGroupTest : public VersionSetTestBase,
|
||||
}
|
||||
edits_[i].SetLastSequence(last_seqno_++);
|
||||
}
|
||||
ASSERT_OK(SetCurrentFile(WriteOptions(), fs_.get(), dbname_, 1, nullptr));
|
||||
CreateCurrentFile();
|
||||
}
|
||||
|
||||
void SetupIncorrectAtomicGroup(int atomic_group_size) {
|
||||
@@ -2642,7 +2578,7 @@ class VersionSetAtomicGroupTest : public VersionSetTestBase,
|
||||
}
|
||||
edits_[i].SetLastSequence(last_seqno_++);
|
||||
}
|
||||
ASSERT_OK(SetCurrentFile(WriteOptions(), fs_.get(), dbname_, 1, nullptr));
|
||||
CreateCurrentFile();
|
||||
}
|
||||
|
||||
void SetupTestSyncPoints() {
|
||||
@@ -3408,8 +3344,7 @@ TEST_P(VersionSetTestDropOneCF, HandleDroppedColumnFamilyInAtomicGroup) {
|
||||
SequenceNumber last_seqno;
|
||||
std::unique_ptr<log::Writer> log_writer;
|
||||
PrepareManifest(&column_families, &last_seqno, &log_writer);
|
||||
Status s = SetCurrentFile(WriteOptions(), fs_.get(), dbname_, 1, nullptr);
|
||||
ASSERT_OK(s);
|
||||
CreateCurrentFile();
|
||||
|
||||
EXPECT_OK(versions_->Recover(column_families, false /* read_only */));
|
||||
EXPECT_EQ(column_families.size(),
|
||||
@@ -3431,7 +3366,7 @@ TEST_P(VersionSetTestDropOneCF, HandleDroppedColumnFamilyInAtomicGroup) {
|
||||
cfd_to_drop->Ref();
|
||||
drop_cf_edit.SetColumnFamily(cfd_to_drop->GetID());
|
||||
mutex_.Lock();
|
||||
s = versions_->LogAndApply(
|
||||
Status s = versions_->LogAndApply(
|
||||
cfd_to_drop, *cfd_to_drop->GetLatestMutableCFOptions(), read_options,
|
||||
write_options, &drop_cf_edit, &mutex_, nullptr);
|
||||
mutex_.Unlock();
|
||||
@@ -3541,9 +3476,7 @@ class EmptyDefaultCfNewManifest : public VersionSetTestBase,
|
||||
TEST_F(EmptyDefaultCfNewManifest, Recover) {
|
||||
PrepareManifest(nullptr, nullptr, &log_writer_);
|
||||
log_writer_.reset();
|
||||
Status s = SetCurrentFile(WriteOptions(), fs_.get(), dbname_, 1,
|
||||
/* dir_contains_current_file */ nullptr);
|
||||
ASSERT_OK(s);
|
||||
CreateCurrentFile();
|
||||
std::string manifest_path;
|
||||
VerifyManifest(&manifest_path);
|
||||
std::vector<ColumnFamilyDescriptor> column_families;
|
||||
@@ -3552,7 +3485,7 @@ TEST_F(EmptyDefaultCfNewManifest, Recover) {
|
||||
cf_options_);
|
||||
std::string db_id;
|
||||
bool has_missing_table_file = false;
|
||||
s = versions_->TryRecoverFromOneManifest(
|
||||
Status s = versions_->TryRecoverFromOneManifest(
|
||||
manifest_path, column_families, false, &db_id, &has_missing_table_file);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_FALSE(has_missing_table_file);
|
||||
@@ -3573,7 +3506,8 @@ class VersionSetTestEmptyDb
|
||||
assert(nullptr != log_writer);
|
||||
VersionEdit new_db;
|
||||
if (db_options_.write_dbid_to_manifest) {
|
||||
ASSERT_OK(SetIdentityFile(WriteOptions(), env_, dbname_));
|
||||
ASSERT_OK(SetIdentityFile(WriteOptions(), env_, dbname_,
|
||||
Temperature::kUnknown));
|
||||
DBOptions tmp_db_options;
|
||||
tmp_db_options.env = env_;
|
||||
std::unique_ptr<DBImpl> impl(new DBImpl(tmp_db_options, dbname_));
|
||||
@@ -3606,9 +3540,7 @@ TEST_P(VersionSetTestEmptyDb, OpenFromIncompleteManifest0) {
|
||||
db_options_.write_dbid_to_manifest = std::get<0>(GetParam());
|
||||
PrepareManifest(nullptr, nullptr, &log_writer_);
|
||||
log_writer_.reset();
|
||||
Status s = SetCurrentFile(WriteOptions(), fs_.get(), dbname_, 1,
|
||||
/* dir_contains_current_file */ nullptr);
|
||||
ASSERT_OK(s);
|
||||
CreateCurrentFile();
|
||||
|
||||
std::string manifest_path;
|
||||
VerifyManifest(&manifest_path);
|
||||
@@ -3623,9 +3555,9 @@ TEST_P(VersionSetTestEmptyDb, OpenFromIncompleteManifest0) {
|
||||
|
||||
std::string db_id;
|
||||
bool has_missing_table_file = false;
|
||||
s = versions_->TryRecoverFromOneManifest(manifest_path, column_families,
|
||||
read_only, &db_id,
|
||||
&has_missing_table_file);
|
||||
Status s = versions_->TryRecoverFromOneManifest(
|
||||
manifest_path, column_families, read_only, &db_id,
|
||||
&has_missing_table_file);
|
||||
auto iter =
|
||||
std::find(cf_names.begin(), cf_names.end(), kDefaultColumnFamilyName);
|
||||
if (iter == cf_names.end()) {
|
||||
@@ -3651,9 +3583,7 @@ TEST_P(VersionSetTestEmptyDb, OpenFromIncompleteManifest1) {
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
log_writer_.reset();
|
||||
s = SetCurrentFile(WriteOptions(), fs_.get(), dbname_, 1,
|
||||
/* dir_contains_current_file */ nullptr);
|
||||
ASSERT_OK(s);
|
||||
CreateCurrentFile();
|
||||
|
||||
std::string manifest_path;
|
||||
VerifyManifest(&manifest_path);
|
||||
@@ -3699,9 +3629,7 @@ TEST_P(VersionSetTestEmptyDb, OpenFromInCompleteManifest2) {
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
log_writer_.reset();
|
||||
s = SetCurrentFile(WriteOptions(), fs_.get(), dbname_, 1,
|
||||
/* dir_contains_current_file */ nullptr);
|
||||
ASSERT_OK(s);
|
||||
CreateCurrentFile();
|
||||
|
||||
std::string manifest_path;
|
||||
VerifyManifest(&manifest_path);
|
||||
@@ -3758,9 +3686,7 @@ TEST_P(VersionSetTestEmptyDb, OpenManifestWithUnknownCF) {
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
log_writer_.reset();
|
||||
s = SetCurrentFile(WriteOptions(), fs_.get(), dbname_, 1,
|
||||
/* dir_contains_current_file */ nullptr);
|
||||
ASSERT_OK(s);
|
||||
CreateCurrentFile();
|
||||
|
||||
std::string manifest_path;
|
||||
VerifyManifest(&manifest_path);
|
||||
@@ -3816,9 +3742,7 @@ TEST_P(VersionSetTestEmptyDb, OpenCompleteManifest) {
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
log_writer_.reset();
|
||||
s = SetCurrentFile(WriteOptions(), fs_.get(), dbname_, 1,
|
||||
/* dir_contains_current_file */ nullptr);
|
||||
ASSERT_OK(s);
|
||||
CreateCurrentFile();
|
||||
|
||||
std::string manifest_path;
|
||||
VerifyManifest(&manifest_path);
|
||||
@@ -3896,6 +3820,8 @@ class VersionSetTestMissingFiles : public VersionSetTestBase,
|
||||
assert(column_families != nullptr);
|
||||
assert(last_seqno != nullptr);
|
||||
assert(log_writer != nullptr);
|
||||
ASSERT_OK(
|
||||
SetIdentityFile(WriteOptions(), env_, dbname_, Temperature::kUnknown));
|
||||
const std::string manifest = DescriptorFileName(dbname_, 1);
|
||||
const auto& fs = env_->GetFileSystem();
|
||||
std::unique_ptr<WritableFileWriter> file_writer;
|
||||
@@ -4025,15 +3951,14 @@ TEST_F(VersionSetTestMissingFiles, ManifestFarBehindSst) {
|
||||
WriteFileAdditionAndDeletionToManifest(
|
||||
/*cf=*/0, std::vector<std::pair<int, FileMetaData>>(), deleted_files);
|
||||
log_writer_.reset();
|
||||
Status s = SetCurrentFile(WriteOptions(), fs_.get(), dbname_, 1, nullptr);
|
||||
ASSERT_OK(s);
|
||||
CreateCurrentFile();
|
||||
std::string manifest_path;
|
||||
VerifyManifest(&manifest_path);
|
||||
std::string db_id;
|
||||
bool has_missing_table_file = false;
|
||||
s = versions_->TryRecoverFromOneManifest(manifest_path, column_families_,
|
||||
/*read_only=*/false, &db_id,
|
||||
&has_missing_table_file);
|
||||
Status s = versions_->TryRecoverFromOneManifest(
|
||||
manifest_path, column_families_,
|
||||
/*read_only=*/false, &db_id, &has_missing_table_file);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_TRUE(has_missing_table_file);
|
||||
for (ColumnFamilyData* cfd : *(versions_->GetColumnFamilySet())) {
|
||||
@@ -4083,15 +4008,14 @@ TEST_F(VersionSetTestMissingFiles, ManifestAheadofSst) {
|
||||
WriteFileAdditionAndDeletionToManifest(
|
||||
/*cf=*/0, added_files, std::vector<std::pair<int, uint64_t>>());
|
||||
log_writer_.reset();
|
||||
Status s = SetCurrentFile(WriteOptions(), fs_.get(), dbname_, 1, nullptr);
|
||||
ASSERT_OK(s);
|
||||
CreateCurrentFile();
|
||||
std::string manifest_path;
|
||||
VerifyManifest(&manifest_path);
|
||||
std::string db_id;
|
||||
bool has_missing_table_file = false;
|
||||
s = versions_->TryRecoverFromOneManifest(manifest_path, column_families_,
|
||||
/*read_only=*/false, &db_id,
|
||||
&has_missing_table_file);
|
||||
Status s = versions_->TryRecoverFromOneManifest(
|
||||
manifest_path, column_families_,
|
||||
/*read_only=*/false, &db_id, &has_missing_table_file);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_TRUE(has_missing_table_file);
|
||||
for (ColumnFamilyData* cfd : *(versions_->GetColumnFamilySet())) {
|
||||
@@ -4137,15 +4061,14 @@ TEST_F(VersionSetTestMissingFiles, NoFileMissing) {
|
||||
WriteFileAdditionAndDeletionToManifest(
|
||||
/*cf=*/0, std::vector<std::pair<int, FileMetaData>>(), deleted_files);
|
||||
log_writer_.reset();
|
||||
Status s = SetCurrentFile(WriteOptions(), fs_.get(), dbname_, 1, nullptr);
|
||||
ASSERT_OK(s);
|
||||
CreateCurrentFile();
|
||||
std::string manifest_path;
|
||||
VerifyManifest(&manifest_path);
|
||||
std::string db_id;
|
||||
bool has_missing_table_file = false;
|
||||
s = versions_->TryRecoverFromOneManifest(manifest_path, column_families_,
|
||||
/*read_only=*/false, &db_id,
|
||||
&has_missing_table_file);
|
||||
Status s = versions_->TryRecoverFromOneManifest(
|
||||
manifest_path, column_families_,
|
||||
/*read_only=*/false, &db_id, &has_missing_table_file);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_FALSE(has_missing_table_file);
|
||||
for (ColumnFamilyData* cfd : *(versions_->GetColumnFamilySet())) {
|
||||
@@ -4266,15 +4189,14 @@ class BestEffortsRecoverIncompleteVersionTest
|
||||
/*cf=*/0, added_files, std::vector<std::pair<int, uint64_t>>(),
|
||||
blob_files);
|
||||
log_writer_.reset();
|
||||
Status s = SetCurrentFile(WriteOptions(), fs_.get(), dbname_, 1, nullptr);
|
||||
ASSERT_OK(s);
|
||||
CreateCurrentFile();
|
||||
std::string manifest_path;
|
||||
VerifyManifest(&manifest_path);
|
||||
std::string db_id;
|
||||
bool has_missing_table_file = false;
|
||||
s = versions_->TryRecoverFromOneManifest(manifest_path, column_families_,
|
||||
/*read_only=*/false, &db_id,
|
||||
&has_missing_table_file);
|
||||
Status s = versions_->TryRecoverFromOneManifest(
|
||||
manifest_path, column_families_,
|
||||
/*read_only=*/false, &db_id, &has_missing_table_file);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_TRUE(has_missing_table_file);
|
||||
}
|
||||
|
||||
+6
-2
@@ -502,7 +502,9 @@ Status ReadRecordFromWriteBatch(Slice* input, char* tag,
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return Status::Corruption("unknown WriteBatch tag");
|
||||
return Status::Corruption(
|
||||
"unknown WriteBatch tag",
|
||||
std::to_string(static_cast<unsigned int>(*tag)));
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
@@ -750,7 +752,9 @@ Status WriteBatchInternal::Iterate(const WriteBatch* wb,
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return Status::Corruption("unknown WriteBatch tag");
|
||||
return Status::Corruption(
|
||||
"unknown WriteBatch tag",
|
||||
std::to_string(static_cast<unsigned int>(tag)));
|
||||
}
|
||||
}
|
||||
if (!s.ok()) {
|
||||
|
||||
@@ -59,7 +59,7 @@ static std::string PrintContents(WriteBatch* b,
|
||||
InternalIterator* iter;
|
||||
if (i == 0) {
|
||||
iter = mem->NewIterator(ReadOptions(), /*seqno_to_time_mapping=*/nullptr,
|
||||
&arena);
|
||||
&arena, /*prefix_extractor=*/nullptr);
|
||||
arena_iter_guard.reset(iter);
|
||||
} else {
|
||||
iter = mem->NewRangeTombstoneIterator(ReadOptions(),
|
||||
|
||||
@@ -260,6 +260,7 @@ DECLARE_bool(use_full_merge_v1);
|
||||
DECLARE_int32(sync_wal_one_in);
|
||||
DECLARE_bool(avoid_unnecessary_blocking_io);
|
||||
DECLARE_bool(write_dbid_to_manifest);
|
||||
DECLARE_bool(write_identity_file);
|
||||
DECLARE_bool(avoid_flush_during_recovery);
|
||||
DECLARE_uint64(max_write_batch_group_size_bytes);
|
||||
DECLARE_bool(level_compaction_dynamic_level_bytes);
|
||||
|
||||
@@ -497,7 +497,7 @@ DEFINE_double(blob_garbage_collection_force_threshold,
|
||||
ROCKSDB_NAMESPACE::AdvancedColumnFamilyOptions()
|
||||
.blob_garbage_collection_force_threshold,
|
||||
"[Integrated BlobDB] The threshold for the ratio of garbage in "
|
||||
"the oldest blob files for forcing garbage collection.");
|
||||
"the eligible blob files for forcing garbage collection.");
|
||||
|
||||
DEFINE_uint64(blob_compaction_readahead_size,
|
||||
ROCKSDB_NAMESPACE::AdvancedColumnFamilyOptions()
|
||||
@@ -993,6 +993,10 @@ DEFINE_bool(write_dbid_to_manifest,
|
||||
ROCKSDB_NAMESPACE::Options().write_dbid_to_manifest,
|
||||
"Write DB_ID to manifest");
|
||||
|
||||
DEFINE_bool(write_identity_file,
|
||||
ROCKSDB_NAMESPACE::Options().write_identity_file,
|
||||
"Write DB_ID to IDENTITY file");
|
||||
|
||||
DEFINE_bool(avoid_flush_during_recovery,
|
||||
ROCKSDB_NAMESPACE::Options().avoid_flush_during_recovery,
|
||||
"Avoid flush during recovery");
|
||||
|
||||
@@ -1592,7 +1592,7 @@ Status StressTest::TestIterateImpl(ThreadState* thread,
|
||||
ro.total_order_seek = true;
|
||||
expect_total_order = true;
|
||||
} else if (thread->rand.OneIn(4)) {
|
||||
ro.total_order_seek = false;
|
||||
ro.total_order_seek = thread->rand.OneIn(2);
|
||||
ro.auto_prefix_mode = true;
|
||||
expect_total_order = true;
|
||||
} else if (options_.prefix_extractor.get() == nullptr) {
|
||||
@@ -4044,6 +4044,7 @@ void InitializeOptionsFromFlags(
|
||||
options.manual_wal_flush = FLAGS_manual_wal_flush_one_in > 0 ? true : false;
|
||||
options.avoid_unnecessary_blocking_io = FLAGS_avoid_unnecessary_blocking_io;
|
||||
options.write_dbid_to_manifest = FLAGS_write_dbid_to_manifest;
|
||||
options.write_identity_file = FLAGS_write_identity_file;
|
||||
options.avoid_flush_during_recovery = FLAGS_avoid_flush_during_recovery;
|
||||
options.max_write_batch_group_size_bytes =
|
||||
FLAGS_max_write_batch_group_size_bytes;
|
||||
|
||||
@@ -732,8 +732,31 @@ Status FileExpectedStateManager::Restore(DB* db) {
|
||||
s = Env::Default()->DeleteFile(state_file_path);
|
||||
}
|
||||
if (s.ok()) {
|
||||
saved_seqno_ = kMaxSequenceNumber;
|
||||
s = Env::Default()->DeleteFile(trace_file_path);
|
||||
std::vector<std::string> expected_state_dir_children;
|
||||
s = Env::Default()->GetChildren(expected_state_dir_path_,
|
||||
&expected_state_dir_children);
|
||||
if (s.ok()) {
|
||||
for (size_t i = 0; i < expected_state_dir_children.size(); ++i) {
|
||||
const auto& filename = expected_state_dir_children[i];
|
||||
if (filename.size() >= kTraceFilenameSuffix.size() &&
|
||||
filename.rfind(kTraceFilenameSuffix) ==
|
||||
filename.size() - kTraceFilenameSuffix.size()) {
|
||||
SequenceNumber found_seqno = ParseUint64(filename.substr(
|
||||
0, filename.size() - kTraceFilenameSuffix.size()));
|
||||
// Delete older trace files, but keep the one we just replayed for
|
||||
// debugging purposes
|
||||
if (found_seqno < saved_seqno_) {
|
||||
s = Env::Default()->DeleteFile(GetPathForFilename(filename));
|
||||
}
|
||||
}
|
||||
if (!s.ok()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (s.ok()) {
|
||||
saved_seqno_ = kMaxSequenceNumber;
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
Vendored
+3
-3
@@ -181,10 +181,10 @@ FileOptions FileSystem::OptimizeForBlobFileRead(
|
||||
|
||||
IOStatus WriteStringToFile(FileSystem* fs, const Slice& data,
|
||||
const std::string& fname, bool should_sync,
|
||||
const IOOptions& io_options) {
|
||||
const IOOptions& io_options,
|
||||
const FileOptions& file_options) {
|
||||
std::unique_ptr<FSWritableFile> file;
|
||||
EnvOptions soptions;
|
||||
IOStatus s = fs->NewWritableFile(fname, soptions, &file, nullptr);
|
||||
IOStatus s = fs->NewWritableFile(fname, file_options, &file, nullptr);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
Vendored
+11
@@ -77,6 +77,17 @@ IOStatus RemapFileSystem::NewWritableFile(
|
||||
result, dbg);
|
||||
}
|
||||
|
||||
IOStatus RemapFileSystem::ReopenWritableFile(
|
||||
const std::string& fname, const FileOptions& options,
|
||||
std::unique_ptr<FSWritableFile>* result, IODebugContext* dbg) {
|
||||
auto status_and_enc_path = EncodePathWithNewBasename(fname);
|
||||
if (!status_and_enc_path.first.ok()) {
|
||||
return status_and_enc_path.first;
|
||||
}
|
||||
return FileSystemWrapper::ReopenWritableFile(status_and_enc_path.second,
|
||||
options, result, dbg);
|
||||
}
|
||||
|
||||
IOStatus RemapFileSystem::ReuseWritableFile(
|
||||
const std::string& fname, const std::string& old_fname,
|
||||
const FileOptions& options, std::unique_ptr<FSWritableFile>* result,
|
||||
|
||||
Vendored
+5
@@ -65,6 +65,11 @@ class RemapFileSystem : public FileSystemWrapper {
|
||||
std::unique_ptr<FSWritableFile>* result,
|
||||
IODebugContext* dbg) override;
|
||||
|
||||
IOStatus ReopenWritableFile(const std::string& fname,
|
||||
const FileOptions& options,
|
||||
std::unique_ptr<FSWritableFile>* result,
|
||||
IODebugContext* dbg) override;
|
||||
|
||||
IOStatus ReuseWritableFile(const std::string& fname,
|
||||
const std::string& old_fname,
|
||||
const FileOptions& options,
|
||||
|
||||
+11
-3
@@ -388,6 +388,7 @@ bool ParseFileName(const std::string& fname, uint64_t* number,
|
||||
|
||||
IOStatus SetCurrentFile(const WriteOptions& write_options, FileSystem* fs,
|
||||
const std::string& dbname, uint64_t descriptor_number,
|
||||
Temperature temp,
|
||||
FSDirectory* dir_contains_current_file) {
|
||||
// Remove leading "dbname/" and add newline to manifest file name
|
||||
std::string manifest = DescriptorFileName(dbname, descriptor_number);
|
||||
@@ -397,8 +398,11 @@ IOStatus SetCurrentFile(const WriteOptions& write_options, FileSystem* fs,
|
||||
std::string tmp = TempFileName(dbname, descriptor_number);
|
||||
IOOptions opts;
|
||||
IOStatus s = PrepareIOFromWriteOptions(write_options, opts);
|
||||
FileOptions file_opts;
|
||||
file_opts.temperature = temp;
|
||||
if (s.ok()) {
|
||||
s = WriteStringToFile(fs, contents.ToString() + "\n", tmp, true, opts);
|
||||
s = WriteStringToFile(fs, contents.ToString() + "\n", tmp, true, opts,
|
||||
file_opts);
|
||||
}
|
||||
TEST_SYNC_POINT_CALLBACK("SetCurrentFile:BeforeRename", &s);
|
||||
if (s.ok()) {
|
||||
@@ -423,7 +427,8 @@ IOStatus SetCurrentFile(const WriteOptions& write_options, FileSystem* fs,
|
||||
}
|
||||
|
||||
Status SetIdentityFile(const WriteOptions& write_options, Env* env,
|
||||
const std::string& dbname, const std::string& db_id) {
|
||||
const std::string& dbname, Temperature temp,
|
||||
const std::string& db_id) {
|
||||
std::string id;
|
||||
if (db_id.empty()) {
|
||||
id = env->GenerateUniqueId();
|
||||
@@ -437,8 +442,11 @@ Status SetIdentityFile(const WriteOptions& write_options, Env* env,
|
||||
Status s;
|
||||
IOOptions opts;
|
||||
s = PrepareIOFromWriteOptions(write_options, opts);
|
||||
FileOptions file_opts;
|
||||
file_opts.temperature = temp;
|
||||
if (s.ok()) {
|
||||
s = WriteStringToFile(env, id, tmp, true, &opts);
|
||||
s = WriteStringToFile(env->GetFileSystem().get(), id, tmp,
|
||||
/*should_sync=*/true, opts, file_opts);
|
||||
}
|
||||
if (s.ok()) {
|
||||
s = env->RenameFile(tmp, identify_file_name);
|
||||
|
||||
+2
-1
@@ -161,11 +161,12 @@ bool ParseFileName(const std::string& filename, uint64_t* number,
|
||||
// when
|
||||
IOStatus SetCurrentFile(const WriteOptions& write_options, FileSystem* fs,
|
||||
const std::string& dbname, uint64_t descriptor_number,
|
||||
Temperature temp,
|
||||
FSDirectory* dir_contains_current_file);
|
||||
|
||||
// Make the IDENTITY file for the db
|
||||
Status SetIdentityFile(const WriteOptions& write_options, Env* env,
|
||||
const std::string& dbname,
|
||||
const std::string& dbname, Temperature temp,
|
||||
const std::string& db_id = {});
|
||||
|
||||
// Sync manifest file `file`.
|
||||
|
||||
@@ -99,6 +99,7 @@ void SstFileManagerImpl::OnCompactionCompletion(Compaction* c) {
|
||||
size_added_by_compaction += filemeta->fd.GetFileSize();
|
||||
}
|
||||
}
|
||||
assert(cur_compactions_reserved_size_ >= size_added_by_compaction);
|
||||
cur_compactions_reserved_size_ -= size_added_by_compaction;
|
||||
}
|
||||
|
||||
@@ -117,6 +118,16 @@ Status SstFileManagerImpl::OnMoveFile(const std::string& old_path,
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status SstFileManagerImpl::OnUntrackFile(const std::string& file_path) {
|
||||
{
|
||||
MutexLock l(&mu_);
|
||||
OnDeleteFileImpl(file_path);
|
||||
}
|
||||
TEST_SYNC_POINT_CALLBACK("SstFileManagerImpl::OnUntrackFile",
|
||||
const_cast<std::string*>(&file_path));
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
void SstFileManagerImpl::SetMaxAllowedSpaceUsage(uint64_t max_allowed_space) {
|
||||
MutexLock l(&mu_);
|
||||
max_allowed_space_ = max_allowed_space;
|
||||
@@ -450,7 +461,6 @@ void SstFileManagerImpl::OnAddFileImpl(const std::string& file_path,
|
||||
// File was added before, we will just update the size
|
||||
total_files_size_ -= tracked_file->second;
|
||||
total_files_size_ += file_size;
|
||||
cur_compactions_reserved_size_ -= file_size;
|
||||
} else {
|
||||
total_files_size_ += file_size;
|
||||
}
|
||||
|
||||
@@ -50,6 +50,9 @@ class SstFileManagerImpl : public SstFileManager {
|
||||
Status OnMoveFile(const std::string& old_path, const std::string& new_path,
|
||||
uint64_t* file_size = nullptr);
|
||||
|
||||
// DB will call OnUntrackFile when closing with an unowned SstFileManager.
|
||||
Status OnUntrackFile(const std::string& file_path);
|
||||
|
||||
// Update the maximum allowed space that should be used by RocksDB, if
|
||||
// the total size of the SST and blob files exceeds max_allowed_space, writes
|
||||
// to RocksDB will fail.
|
||||
@@ -217,4 +220,3 @@ class SstFileManagerImpl : public SstFileManager {
|
||||
};
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
@@ -41,7 +41,7 @@ class Statistics;
|
||||
//
|
||||
// INTERNAL: See typed_cache.h for convenient wrappers on top of this API.
|
||||
// New virtual functions must also be added to CacheWrapper below.
|
||||
class Cache {
|
||||
class Cache : public Customizable {
|
||||
public: // types hidden from API client
|
||||
// Opaque handle to an entry stored in the cache.
|
||||
struct Handle {};
|
||||
@@ -190,6 +190,8 @@ class Cache {
|
||||
// Destroys all remaining entries by calling the associated "deleter"
|
||||
virtual ~Cache() {}
|
||||
|
||||
static const char* Type() { return "Cache"; }
|
||||
|
||||
// Creates a new Cache based on the input value string and returns the result.
|
||||
// Currently, this method can be used to create LRUCaches only
|
||||
// @param config_options
|
||||
@@ -411,6 +413,14 @@ class Cache {
|
||||
const CacheItemHelper* helper)>& callback,
|
||||
const ApplyToAllEntriesOptions& opts) = 0;
|
||||
|
||||
// Apply a callback to a cache handle. The Cache must ensure the lifetime
|
||||
// of the key passed to the callback is valid for the duration of the
|
||||
// callback. The handle may not belong to the cache, but is guaranteed to
|
||||
// be type compatible.
|
||||
virtual void ApplyToHandle(
|
||||
Cache* cache, Handle* handle,
|
||||
const std::function<void(const Slice& key, ObjectPtr obj, size_t charge,
|
||||
const CacheItemHelper* helper)>& callback) = 0;
|
||||
// Remove all entries.
|
||||
// Prerequisite: no entry is referenced.
|
||||
virtual void EraseUnRefEntries() = 0;
|
||||
@@ -636,6 +646,15 @@ class CacheWrapper : public Cache {
|
||||
target_->ApplyToAllEntries(callback, opts);
|
||||
}
|
||||
|
||||
virtual void ApplyToHandle(
|
||||
Cache* cache, Handle* handle,
|
||||
const std::function<void(const Slice& key, ObjectPtr obj, size_t charge,
|
||||
const CacheItemHelper* helper)>& callback)
|
||||
override {
|
||||
auto cache_ptr = static_cast<CacheWrapper*>(cache);
|
||||
target_->ApplyToHandle(cache_ptr->target_.get(), handle, callback);
|
||||
}
|
||||
|
||||
void EraseUnRefEntries() override { target_->EraseUnRefEntries(); }
|
||||
|
||||
void StartAsyncLookup(AsyncLookupHandle& async_handle) override {
|
||||
|
||||
Loaded 100 of 166 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user