mirror of
https://github.com/facebook/rocksdb.git
synced 2026-11-07 11:32:34 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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,
|
||||
|
||||
+26
-4
@@ -1,13 +1,35 @@
|
||||
# Rocksdb Change Log
|
||||
> NOTE: Entries for next release do not go here. Follow instructions in `unreleased_history/README.txt`
|
||||
|
||||
## 9.6.2 (10/31/2024)
|
||||
## 9.7.2 (10/08/2024)
|
||||
### Bug Fixes
|
||||
* Fix a leak of obsolete blob files left open until DB::Close(). This bug was introduced in version 9.4.0.
|
||||
* Fix a bug for surfacing write unix time: `Iterator::GetProperty("rocksdb.iterator.write-time")` for non-L0 files.
|
||||
|
||||
## 9.6.1 (08/24/2024)
|
||||
## 9.7.1 (09/26/2024)
|
||||
### Bug Fixes
|
||||
* Fix correctness of MultiGet across column families with user timestamp.
|
||||
* 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
|
||||
|
||||
@@ -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,7 +42,6 @@ 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_);
|
||||
@@ -99,13 +98,4 @@ 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,15 +36,6 @@ 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
|
||||
|
||||
@@ -401,7 +401,6 @@ 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
|
||||
@@ -512,8 +511,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);
|
||||
}
|
||||
|
||||
@@ -377,9 +377,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;
|
||||
|
||||
@@ -402,9 +400,6 @@ struct CompactionServiceInput {
|
||||
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);
|
||||
|
||||
@@ -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,15 +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_, Temperature::kUnknown));
|
||||
|
||||
VersionEdit new_db;
|
||||
new_db.SetLogNumber(0);
|
||||
@@ -1569,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));
|
||||
}
|
||||
@@ -1607,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;
|
||||
|
||||
@@ -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 =
|
||||
@@ -70,10 +66,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 +83,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 +99,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 +109,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 +134,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 +162,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
|
||||
@@ -261,11 +261,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;
|
||||
@@ -336,6 +336,7 @@ Status CompactionServiceCompactionJob::Run() {
|
||||
// Build compaction result
|
||||
compaction_result_->output_level = compact_->compaction->output_level();
|
||||
compaction_result_->output_path = output_path_;
|
||||
compaction_result_->stats.is_remote_compaction = true;
|
||||
for (const auto& output_file : sub_compact->GetOutputs()) {
|
||||
auto& meta = output_file.meta;
|
||||
compaction_result_->output_files.emplace_back(
|
||||
@@ -404,42 +405,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,
|
||||
@@ -563,6 +531,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,
|
||||
|
||||
@@ -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,14 @@ 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_TRUE(result.stats.is_remote_compaction);
|
||||
Close();
|
||||
}
|
||||
|
||||
@@ -369,6 +388,12 @@ 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, CancelCompactionOnRemoteSide) {
|
||||
@@ -601,11 +626,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 +662,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) {
|
||||
|
||||
@@ -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());
|
||||
|
||||
+92
-20
@@ -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,9 +659,8 @@ 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, and (for certain builds/conditions) assert that no obsolete
|
||||
// files are hanging around unreferenced (leak) in the table/blob file cache.
|
||||
// versions_.reset() may cause issues.
|
||||
// Here we clean all unreferenced handles in table 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
|
||||
@@ -664,15 +668,19 @@ 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.
|
||||
{
|
||||
@@ -1530,7 +1538,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);
|
||||
}
|
||||
@@ -1687,7 +1695,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(
|
||||
@@ -2058,15 +2066,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;
|
||||
@@ -2090,6 +2102,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);
|
||||
@@ -3214,8 +3227,6 @@ 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)
|
||||
@@ -3837,6 +3848,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(
|
||||
@@ -4038,6 +4052,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,
|
||||
@@ -4288,7 +4305,7 @@ void DBImpl::ReleaseSnapshot(const Snapshot* s) {
|
||||
->storage_info()
|
||||
->BottommostFilesMarkedForCompaction()
|
||||
.empty()) {
|
||||
SchedulePendingCompaction(cfd);
|
||||
EnqueuePendingCompaction(cfd);
|
||||
MaybeScheduleFlushOrCompaction();
|
||||
cf_scheduled.push_back(cfd);
|
||||
}
|
||||
@@ -5842,11 +5859,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.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6754,6 +6770,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();
|
||||
|
||||
+48
-14
@@ -1151,6 +1151,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,13 +1239,8 @@ 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;
|
||||
#endif // NDEBUG
|
||||
|
||||
// persist stats to column family "_persistent_stats"
|
||||
void PersistStats();
|
||||
@@ -1587,11 +1584,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.
|
||||
@@ -1621,9 +1619,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();
|
||||
@@ -1961,6 +1965,7 @@ class DBImpl : public DB {
|
||||
void ReleaseFileNumberFromPendingOutputs(
|
||||
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);
|
||||
@@ -2179,7 +2184,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);
|
||||
@@ -2192,6 +2197,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();
|
||||
@@ -2221,10 +2230,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);
|
||||
@@ -2951,6 +2977,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
|
||||
|
||||
@@ -1880,7 +1880,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 +2377,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 +2528,7 @@ Status DBImpl::AtomicFlushMemTables(
|
||||
}
|
||||
}
|
||||
GenerateFlushRequest(cfds, flush_reason, &flush_req);
|
||||
SchedulePendingFlush(flush_req);
|
||||
EnqueuePendingFlush(flush_req);
|
||||
MaybeScheduleFlushOrCompaction();
|
||||
}
|
||||
|
||||
@@ -2583,7 +2583,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 +2597,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 +2950,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 +3006,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 +3042,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 +3218,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();
|
||||
@@ -3678,7 +3678,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 +3706,6 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
if (cfd->NeedsCompaction()) {
|
||||
// Yes, we need more compactions!
|
||||
AddToCompactionQueue(cfd);
|
||||
++unscheduled_compactions_;
|
||||
MaybeScheduleFlushOrCompaction();
|
||||
}
|
||||
}
|
||||
@@ -3997,7 +3995,6 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
*(c->mutable_cf_options()));
|
||||
if (!cfd->queued_for_compaction()) {
|
||||
AddToCompactionQueue(cfd);
|
||||
++unscheduled_compactions_;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4269,7 +4266,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,7 +9,6 @@
|
||||
|
||||
#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"
|
||||
@@ -200,6 +199,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(); }
|
||||
@@ -324,49 +328,5 @@ 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();
|
||||
|
||||
+64
-31
@@ -722,6 +722,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,59 +953,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) {
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
+36
-59
@@ -289,28 +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_,
|
||||
immutable_db_options_.metadata_write_temperature);
|
||||
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);
|
||||
@@ -342,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());
|
||||
@@ -528,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) {
|
||||
@@ -674,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);
|
||||
@@ -1662,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",
|
||||
@@ -1681,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;
|
||||
@@ -1987,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,
|
||||
@@ -2170,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,103 @@ 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();
|
||||
// 2. Load the options from latest OPTIONS file
|
||||
DBOptions db_options;
|
||||
ConfigOptions config_options;
|
||||
config_options.env = override_options.env;
|
||||
std::vector<ColumnFamilyDescriptor> all_column_families;
|
||||
s = LoadLatestOptions(config_options, name, &db_options,
|
||||
&all_column_families);
|
||||
// In a very rare scenario, loading options may fail if the options changed by
|
||||
// the primary host at the same time. Just retry once for now.
|
||||
if (!s.ok()) {
|
||||
s = LoadLatestOptions(config_options, name, &db_options,
|
||||
&all_column_families);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
compaction_input.db_options.env = override_options.env;
|
||||
compaction_input.db_options.file_checksum_gen_factory =
|
||||
|
||||
// 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;
|
||||
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;
|
||||
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;
|
||||
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);
|
||||
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(compaction_input.db_options, name, output_directory,
|
||||
column_families, &handles, &db);
|
||||
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);
|
||||
}
|
||||
|
||||
+5
-5
@@ -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,8 +540,6 @@ 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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
+36
-22
@@ -5597,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) {
|
||||
@@ -5987,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.
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -832,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));
|
||||
@@ -1009,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()) {
|
||||
|
||||
@@ -142,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) {
|
||||
|
||||
+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,7 +164,6 @@ 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);
|
||||
}
|
||||
@@ -180,7 +179,6 @@ 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",
|
||||
|
||||
+13
-12
@@ -24,7 +24,6 @@
|
||||
#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"
|
||||
@@ -270,9 +269,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.
|
||||
@@ -744,9 +744,12 @@ class VersionBuilder::Rep {
|
||||
return Status::Corruption("VersionBuilder", oss.str());
|
||||
}
|
||||
|
||||
auto deleter = [vs = version_set_, ioptions = ioptions_,
|
||||
bc = cfd_ ? cfd_->blob_file_cache()
|
||||
: nullptr](SharedBlobFileMetaData* shared_meta) {
|
||||
// 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) {
|
||||
if (vs) {
|
||||
assert(ioptions);
|
||||
assert(!ioptions->cf_paths.empty());
|
||||
@@ -755,9 +758,6 @@ class VersionBuilder::Rep {
|
||||
vs->AddObsoleteBlobFile(shared_meta->GetBlobFileNumber(),
|
||||
ioptions->cf_paths.front().path);
|
||||
}
|
||||
if (bc) {
|
||||
bc->Evict(shared_meta->GetBlobFileNumber());
|
||||
}
|
||||
|
||||
delete shared_meta;
|
||||
};
|
||||
@@ -766,7 +766,7 @@ class VersionBuilder::Rep {
|
||||
blob_file_number, blob_file_addition.GetTotalBlobCount(),
|
||||
blob_file_addition.GetTotalBlobBytes(),
|
||||
blob_file_addition.GetChecksumMethod(),
|
||||
blob_file_addition.GetChecksumValue(), std::move(deleter));
|
||||
blob_file_addition.GetChecksumValue(), deleter);
|
||||
|
||||
mutable_blob_file_metas_.emplace(
|
||||
blob_file_number, MutableBlobFileMetaData(std::move(shared_meta)));
|
||||
@@ -1592,7 +1592,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,
|
||||
|
||||
+40
-30
@@ -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()
|
||||
|
||||
+14
-2
@@ -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,6 +1514,7 @@ 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));
|
||||
}
|
||||
|
||||
|
||||
+8
-76
@@ -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;
|
||||
@@ -1426,8 +1358,6 @@ class VersionSetTestBase {
|
||||
void NewDB() {
|
||||
SequenceNumber last_seqno;
|
||||
std::unique_ptr<log::Writer> log_writer;
|
||||
ASSERT_OK(
|
||||
SetIdentityFile(WriteOptions(), env_, dbname_, Temperature::kUnknown));
|
||||
PrepareManifest(&column_families_, &last_seqno, &log_writer);
|
||||
log_writer.reset();
|
||||
CreateCurrentFile();
|
||||
@@ -3890,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;
|
||||
|
||||
@@ -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
+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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -89,7 +89,10 @@ struct CompactionOptionsFIFO {
|
||||
// Age (in seconds) threshold for different file temperatures.
|
||||
// When not empty, each element specifies an age threshold `age` and a
|
||||
// temperature such that if all the data in a file is older than `age`,
|
||||
// RocksDB will compact the file to the specified `temperature`.
|
||||
// RocksDB will compact the file to the specified `temperature`. Oldest file
|
||||
// will be considered first. Only one file is compacted at a time,
|
||||
// so multiple files qualifying to be compacted to be same temperature
|
||||
// won't be merged together.
|
||||
//
|
||||
// Note:
|
||||
// - Flushed files will always have temperature kUnknown.
|
||||
@@ -929,13 +932,12 @@ struct AdvancedColumnFamilyOptions {
|
||||
// Dynamically changeable through the SetOptions() API
|
||||
double blob_garbage_collection_age_cutoff = 0.25;
|
||||
|
||||
// If the ratio of garbage in the oldest blob files exceeds this threshold,
|
||||
// targeted compactions are scheduled in order to force garbage collecting
|
||||
// the blob files in question, assuming they are all eligible based on the
|
||||
// value of blob_garbage_collection_age_cutoff above. This option is
|
||||
// currently only supported with leveled compactions.
|
||||
// Note that enable_blob_garbage_collection has to be set in order for this
|
||||
// option to have any effect.
|
||||
// If the ratio of garbage in the blob files currently eligible for garbage
|
||||
// collection exceeds this threshold, targeted compactions are scheduled in
|
||||
// order to force garbage collecting the oldest blob files. This option is
|
||||
// currently only supported with leveled compactions. Note that
|
||||
// enable_blob_garbage_collection has to be set in order for this option to
|
||||
// have any effect.
|
||||
//
|
||||
// Default: 1.0
|
||||
//
|
||||
|
||||
@@ -1644,6 +1644,10 @@ extern ROCKSDB_LIBRARY_API unsigned char
|
||||
rocksdb_options_get_write_dbid_to_manifest(rocksdb_options_t*);
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_options_set_write_dbid_to_manifest(
|
||||
rocksdb_options_t*, unsigned char);
|
||||
extern ROCKSDB_LIBRARY_API unsigned char
|
||||
rocksdb_options_get_write_identity_file(rocksdb_options_t*);
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_options_set_write_identity_file(
|
||||
rocksdb_options_t*, unsigned char);
|
||||
|
||||
extern ROCKSDB_LIBRARY_API unsigned char
|
||||
rocksdb_options_get_track_and_verify_wals_in_manifest(rocksdb_options_t*);
|
||||
|
||||
@@ -19,80 +19,82 @@ struct CompactionJobStats {
|
||||
void Add(const CompactionJobStats& stats);
|
||||
|
||||
// the elapsed time of this compaction in microseconds.
|
||||
uint64_t elapsed_micros;
|
||||
uint64_t elapsed_micros = 0;
|
||||
|
||||
// the elapsed CPU time of this compaction in microseconds.
|
||||
uint64_t cpu_micros;
|
||||
uint64_t cpu_micros = 0;
|
||||
|
||||
// Used internally indicating whether a subcompaction's
|
||||
// `num_input_records` is accurate.
|
||||
bool has_num_input_records;
|
||||
bool has_num_input_records = false;
|
||||
// the number of compaction input records.
|
||||
uint64_t num_input_records;
|
||||
uint64_t num_input_records = 0;
|
||||
// the number of blobs read from blob files
|
||||
uint64_t num_blobs_read;
|
||||
uint64_t num_blobs_read = 0;
|
||||
// the number of compaction input files (table files)
|
||||
size_t num_input_files;
|
||||
size_t num_input_files = 0;
|
||||
// the number of compaction input files at the output level (table files)
|
||||
size_t num_input_files_at_output_level;
|
||||
size_t num_input_files_at_output_level = 0;
|
||||
|
||||
// the number of compaction output records.
|
||||
uint64_t num_output_records;
|
||||
uint64_t num_output_records = 0;
|
||||
// the number of compaction output files (table files)
|
||||
size_t num_output_files;
|
||||
size_t num_output_files = 0;
|
||||
// the number of compaction output files (blob files)
|
||||
size_t num_output_files_blob;
|
||||
size_t num_output_files_blob = 0;
|
||||
|
||||
// true if the compaction is a full compaction (all live SST files input)
|
||||
bool is_full_compaction;
|
||||
bool is_full_compaction = false;
|
||||
// true if the compaction is a manual compaction
|
||||
bool is_manual_compaction;
|
||||
bool is_manual_compaction = false;
|
||||
// true if the compaction ran in a remote worker
|
||||
bool is_remote_compaction = false;
|
||||
|
||||
// the total size of table files in the compaction input
|
||||
uint64_t total_input_bytes;
|
||||
uint64_t total_input_bytes = 0;
|
||||
// the total size of blobs read from blob files
|
||||
uint64_t total_blob_bytes_read;
|
||||
uint64_t total_blob_bytes_read = 0;
|
||||
// the total size of table files in the compaction output
|
||||
uint64_t total_output_bytes;
|
||||
uint64_t total_output_bytes = 0;
|
||||
// the total size of blob files in the compaction output
|
||||
uint64_t total_output_bytes_blob;
|
||||
uint64_t total_output_bytes_blob = 0;
|
||||
|
||||
// number of records being replaced by newer record associated with same key.
|
||||
// this could be a new value or a deletion entry for that key so this field
|
||||
// sums up all updated and deleted keys
|
||||
uint64_t num_records_replaced;
|
||||
uint64_t num_records_replaced = 0;
|
||||
|
||||
// the sum of the uncompressed input keys in bytes.
|
||||
uint64_t total_input_raw_key_bytes;
|
||||
uint64_t total_input_raw_key_bytes = 0;
|
||||
// the sum of the uncompressed input values in bytes.
|
||||
uint64_t total_input_raw_value_bytes;
|
||||
uint64_t total_input_raw_value_bytes = 0;
|
||||
|
||||
// the number of deletion entries before compaction. Deletion entries
|
||||
// can disappear after compaction because they expired
|
||||
uint64_t num_input_deletion_records;
|
||||
uint64_t num_input_deletion_records = 0;
|
||||
// number of deletion records that were found obsolete and discarded
|
||||
// because it is not possible to delete any more keys with this entry
|
||||
// (i.e. all possible deletions resulting from it have been completed)
|
||||
uint64_t num_expired_deletion_records;
|
||||
uint64_t num_expired_deletion_records = 0;
|
||||
|
||||
// number of corrupt keys (ParseInternalKey returned false when applied to
|
||||
// the key) encountered and written out.
|
||||
uint64_t num_corrupt_keys;
|
||||
uint64_t num_corrupt_keys = 0;
|
||||
|
||||
// Following counters are only populated if
|
||||
// options.report_bg_io_stats = true;
|
||||
|
||||
// Time spent on file's Append() call.
|
||||
uint64_t file_write_nanos;
|
||||
uint64_t file_write_nanos = 0;
|
||||
|
||||
// Time spent on sync file range.
|
||||
uint64_t file_range_sync_nanos;
|
||||
uint64_t file_range_sync_nanos = 0;
|
||||
|
||||
// Time spent on file fsync.
|
||||
uint64_t file_fsync_nanos;
|
||||
uint64_t file_fsync_nanos = 0;
|
||||
|
||||
// Time spent on preparing file write (fallocate, etc)
|
||||
uint64_t file_prepare_write_nanos;
|
||||
uint64_t file_prepare_write_nanos = 0;
|
||||
|
||||
// 0-terminated strings storing the first 8 bytes of the smallest and
|
||||
// largest key in the output.
|
||||
@@ -102,10 +104,10 @@ struct CompactionJobStats {
|
||||
std::string largest_output_key_prefix;
|
||||
|
||||
// number of single-deletes which do not meet a put
|
||||
uint64_t num_single_del_fallthru;
|
||||
uint64_t num_single_del_fallthru = 0;
|
||||
|
||||
// number of single-deletes which meet something other than a put
|
||||
uint64_t num_single_del_mismatch;
|
||||
uint64_t num_single_del_mismatch = 0;
|
||||
|
||||
// TODO: Add output_to_penultimate_level output information
|
||||
};
|
||||
|
||||
@@ -179,13 +179,18 @@ class Comparator : public Customizable, public CompareInterface {
|
||||
size_t timestamp_size_;
|
||||
};
|
||||
|
||||
// Return a builtin comparator that uses lexicographic byte-wise
|
||||
// ordering. The result remains the property of this module and
|
||||
// must not be deleted.
|
||||
// Return a builtin comparator that uses lexicographic ordering
|
||||
// on unsigned bytes, so the empty string is ordered before everything
|
||||
// else and a sufficiently long string of \xFF orders after anything.
|
||||
// CanKeysWithDifferentByteContentsBeEqual() == false
|
||||
// Returns an immortal pointer that must not be deleted by the caller.
|
||||
const Comparator* BytewiseComparator();
|
||||
|
||||
// Return a builtin comparator that uses reverse lexicographic byte-wise
|
||||
// ordering.
|
||||
// Return a builtin comparator that is the reverse ordering of
|
||||
// BytewiseComparator(), so the empty string is ordered after everything
|
||||
// else and a sufficiently long string of \xFF orders before anything.
|
||||
// CanKeysWithDifferentByteContentsBeEqual() == false
|
||||
// Returns an immortal pointer that must not be deleted by the caller.
|
||||
const Comparator* ReverseBytewiseComparator();
|
||||
|
||||
// Returns a builtin comparator that enables user-defined timestamps (formatted
|
||||
|
||||
@@ -56,7 +56,9 @@ struct ConfigOptions {
|
||||
// setting
|
||||
};
|
||||
|
||||
// When true, any unused options will be ignored and OK will be returned
|
||||
// When true, any unused options will be ignored and OK will be returned.
|
||||
// For options files that appear to be from the current version or earlier,
|
||||
// unknown options are considered corruption regardless of this setting.
|
||||
bool ignore_unknown_options = false;
|
||||
|
||||
// When true, any unsupported options will be ignored and OK will be returned
|
||||
|
||||
+301
-97
@@ -61,99 +61,17 @@ Status UpdateManifestForFilesState(
|
||||
// EXPERIMENTAL new filtering features
|
||||
// ****************************************************************************
|
||||
|
||||
// A class for splitting a key into meaningful pieces, or "segments" for
|
||||
// filtering purposes. Keys can also be put in "categories" to simplify
|
||||
// some configuration and handling. To simplify satisfying some filtering
|
||||
// requirements, the segments must encompass a complete key prefix (or the whole
|
||||
// key) and segments cannot overlap.
|
||||
// KeySegmentsExtractor - A class for splitting a key into meaningful pieces, or
|
||||
// "segments" for filtering purposes. We say the first key segment has segment
|
||||
// ordinal 0, the second has segment ordinal 1, etc. To simplify satisfying some
|
||||
// filtering requirements, the segments must encompass a complete key prefix (or
|
||||
// the whole key). There cannot be gaps between segments (though segments are
|
||||
// allowed to be essentially unused), and segments cannot overlap.
|
||||
//
|
||||
// Once in production, the behavior associated with a particular Name()
|
||||
// cannot change. Introduce a new Name() when introducing new behaviors.
|
||||
// See also SstQueryFilterConfigsManager below.
|
||||
//
|
||||
// OTHER CURRENT LIMITATIONS (maybe relaxed in the future for segments only
|
||||
// needing point query or WHERE filtering):
|
||||
// * Assumes the (default) byte-wise comparator is used.
|
||||
// * Assumes the category contiguousness property: that each category is
|
||||
// contiguous in comparator order. In other words, any key between two keys of
|
||||
// category c must also be in category c.
|
||||
// * Assumes the (weak) segment ordering property (described below) always
|
||||
// holds. (For byte-wise comparator, this is implied by the segment prefix
|
||||
// property, also described below.)
|
||||
// * Not yet compatible with user timestamp feature
|
||||
//
|
||||
// SEGMENT ORDERING PROPERTY: For maximum use in filters, especially for
|
||||
// filtering key range queries, we must have a correspondence between
|
||||
// the lexicographic ordering of key segments and the ordering of keys
|
||||
// they are extracted from. In other words, if we took the segmented keys
|
||||
// and ordered them primarily by (byte-wise) order on segment 0, then
|
||||
// on segment 1, etc., then key order of the original keys would not be
|
||||
// violated. This is the WEAK form of the property, where multiple keys
|
||||
// might generate the same segments, but such keys must be contiguous in
|
||||
// key order. (The STRONG form of the property is potentially more useful,
|
||||
// but for bytewise comparator, it can be inferred from segments satisfying
|
||||
// the weak property by assuming another segment that extends to the end of
|
||||
// the key, which would be empty if the segments already extend to the end
|
||||
// of the key.)
|
||||
//
|
||||
// The segment ordering property is hard to think about directly, but for
|
||||
// bytewise comparator, it is implied by a simpler property to reason about:
|
||||
// the segment prefix property (see below). (NOTE: an example way to satisfy
|
||||
// the segment ordering property while breaking the segment prefix property
|
||||
// is to have a segment delimited by any byte smaller than a certain value,
|
||||
// and not include the delimiter with the segment leading up to the delimiter.
|
||||
// For example, the space character is ordered before other printable
|
||||
// characters, so breaking "foo bar" into "foo", " ", and "bar" would be
|
||||
// legal, but not recommended.)
|
||||
//
|
||||
// SEGMENT PREFIX PROPERTY: If a key generates segments s0, ..., sn (possibly
|
||||
// more beyond sn) and sn does not extend to the end of the key, then all keys
|
||||
// starting with bytes s0+...+sn (concatenated) also generate the same segments
|
||||
// (possibly more). For example, if a key has segment s0 which is less than the
|
||||
// whole key and another key starts with the bytes of s0--or only has the bytes
|
||||
// of s0--then the other key must have the same segment s0. In other words, any
|
||||
// prefix of segments that might not extend to the end of the key must form an
|
||||
// unambiguous prefix code. See
|
||||
// https://en.wikipedia.org/wiki/Prefix_code In other other words, parsing
|
||||
// a key into segments cannot use even a single byte of look-ahead. Upon
|
||||
// processing each byte, the extractor decides whether to cut a segment that
|
||||
// ends with that byte, but not one that ends before that byte. The only
|
||||
// exception is that upon reaching the end of the key, the extractor can choose
|
||||
// whether to make a segment that ends at the end of the key.
|
||||
//
|
||||
// Example types of key segments that can be freely mixed in any order:
|
||||
// * Some fixed number of bytes or codewords.
|
||||
// * Ends in a delimiter byte or codeword. (Not including the delimiter as
|
||||
// part of the segment leading up to it would very likely violate the segment
|
||||
// prefix property.)
|
||||
// * Length-encoded sequence of bytes or codewords. The length could even
|
||||
// come from a preceding segment.
|
||||
// * Any/all remaining bytes to the end of the key, though this implies all
|
||||
// subsequent segments will be empty.
|
||||
// For each kind of segment, it should be determined before parsing the segment
|
||||
// whether an incomplete/short parse will be treated as a segment extending to
|
||||
// the end of the key or as an empty segment.
|
||||
//
|
||||
// For example, keys might consist of
|
||||
// * Segment 0: Any sequence of bytes up to and including the first ':'
|
||||
// character, or the whole key if no ':' is present.
|
||||
// * Segment 1: The next four bytes, all or nothing (in case of short key).
|
||||
// * Segment 2: An unsigned byte indicating the number of additional bytes in
|
||||
// the segment, and then that many bytes (or less up to the end of the key).
|
||||
// * Segment 3: Any/all remaining bytes in the key
|
||||
//
|
||||
// For an example of what can go wrong, consider using '4' as a delimiter
|
||||
// but not including it with the segment leading up to it. Suppose we have
|
||||
// these keys and corresponding first segments:
|
||||
// "123456" -> "123"
|
||||
// "124536" -> "12"
|
||||
// "125436" -> "125"
|
||||
// Notice how byte-wise comparator ordering of the segments does not follow
|
||||
// the ordering of the keys. This means we cannot safely use a filter with
|
||||
// a range of segment values for filtering key range queries.
|
||||
//
|
||||
// Also note that it is legal for all keys in a category (or many categories)
|
||||
// to return an empty sequence of segments.
|
||||
// Keys can also be put in "categories" to simplify some configuration and
|
||||
// handling. A "legal" key or bound is one that does not return an error (as a
|
||||
// special, unused category) from the extractor. It is also allowed for all
|
||||
// keys in a category to return an empty sequence of segments.
|
||||
//
|
||||
// To eliminate a confusing distinction between a segment that is empty vs.
|
||||
// "not present" for a particular key, each key is logically assiciated with
|
||||
@@ -161,6 +79,280 @@ Status UpdateManifestForFilesState(
|
||||
// segments. In practice, we only represent a finite sequence that (at least)
|
||||
// covers the non-trivial segments.
|
||||
//
|
||||
// Once in production, the behavior associated with a particular GetId()
|
||||
// cannot change. Introduce a new GetId() when introducing new behaviors.
|
||||
// See also SstQueryFilterConfigsManager below.
|
||||
//
|
||||
// This feature hasn't yet been validated with user timestamp.
|
||||
//
|
||||
// = A SIMPLIFIED MODEL =
|
||||
// Let us start with the easiest set of contraints to satisfy with a key
|
||||
// segments extractor that generally allows for correct point and range
|
||||
// filtering, and add complexity from there. Here we first assume
|
||||
// * The column family is using the byte-wise comparator, or reverse byte-wise
|
||||
// * A single category is assigned to all keys (by the extractor)
|
||||
// * Using simplified criteria for legal segment extraction, the "segment
|
||||
// maximal prefix property"
|
||||
//
|
||||
// SEGMENT MAXIMAL PREFIX PROPERTY: The segment that a byte is assigned to can
|
||||
// only depend on the bytes that come before it, not on the byte itself nor
|
||||
// anything later including the full length of the key or bound.
|
||||
//
|
||||
// Equivalently, two keys or bounds must agree on the segment assignment of
|
||||
// position i if the two keys share a common byte-wise prefix up to at least
|
||||
// position i - 1 (and i is within bounds of both keys).
|
||||
//
|
||||
// This specifically excludes "all or nothing" segments where it is only
|
||||
// included if it reaches a particular width or delimiter. A segment resembling
|
||||
// the FixedPrefixTransform would be illegal (without other assumptions); it
|
||||
// must be like CappedPrefixTransform.
|
||||
//
|
||||
// This basically matches the notion of parsing prefix codes (see
|
||||
// https://en.wikipedia.org/wiki/Prefix_code) except we have to include any
|
||||
// partial segment (code word) at the end whenever an extension to that key
|
||||
// might produce a full segment. An example would be parsing UTF-8 into
|
||||
// segments corresponding to encoded code points, where any incomplete code
|
||||
// at the end must be part of a trailing segment. Note a three-way
|
||||
// correspondence between
|
||||
// (a) byte-wise ordering of encoded code points, e.g.
|
||||
// { D0 98 E2 82 AC }
|
||||
// { E2 82 AC D0 98 }
|
||||
// (b) lexicographic-then-byte-wise ordering of segments that are each an
|
||||
// encoded code point, e.g.
|
||||
// {{ D0 98 } { E2 82 AC }}
|
||||
// {{ E2 82 AC } { D0 98 }}
|
||||
// and (c) lexicographic ordering of the decoded code points, e.g.
|
||||
// { U+0418 U+20AC }
|
||||
// { U+20AC U+0418 }
|
||||
// The correspondence between (a) and (b) is a result of the segment maximal
|
||||
// prefix property and is critical for correct application of filters to
|
||||
// range queries. The correspondence with (c) is a handy attribute of UTF-8
|
||||
// (with no over-long encodings) and might be useful to the application.
|
||||
//
|
||||
// Example types of key segments that can be freely mixed in any order:
|
||||
// * Capped number of bytes or codewords. The number cap for the segment
|
||||
// could be the same for all keys or encoded earlier in the key.
|
||||
// * Up to *and including* a delimiter byte or codeword.
|
||||
// * Any/all remaining bytes to the end of the key, though this implies all
|
||||
// subsequent segments will be empty.
|
||||
// As part of the segment maximal prefix property, if the segments do not
|
||||
// extend to the end of the key, that must be implied by the bytes that are
|
||||
// in segments, NOT because the potential contents of a segment were considered
|
||||
// incomplete.
|
||||
//
|
||||
// For example, keys might consist of
|
||||
// * Segment 0: Any sequence of bytes up to and including the first ':'
|
||||
// character, or the whole key if no ':' is present.
|
||||
// * Segment 1: The next four bytes, or less if we reach end of key.
|
||||
// * Segment 2: An unsigned byte indicating the number of additional bytes in
|
||||
// the segment, and then that many bytes (or less up to the end of the key).
|
||||
// * Segment 3: Any/all remaining bytes in the key
|
||||
//
|
||||
// For an example of what can go wrong, consider using '4' as a delimiter
|
||||
// but not including it with the segment leading up to it. Suppose we have
|
||||
// these keys and corresponding first segments:
|
||||
// "123456" -> "123" (in file 1)
|
||||
// "124536" -> "12" (in file 2)
|
||||
// "125436" -> "125" (in file 1)
|
||||
// Notice how byte-wise comparator ordering of the segments does not follow
|
||||
// the ordering of the keys. This means we cannot safely use a filter with
|
||||
// a range of segment values for filtering key range queries. For example,
|
||||
// we might get a range query for ["123", "125Z") and miss that key "124536"
|
||||
// in file 2 is in range because its first segment "12" is out of the range
|
||||
// of the first segments on the bounds, "123" and "125". We cannot even safely
|
||||
// use this for prefix-like range querying with a Bloom filter on the segments.
|
||||
// For a query ["12", "124Z"), segment "12" would likely not match the Bloom
|
||||
// filter in file 1 and miss "123456".
|
||||
//
|
||||
// CATEGORIES: The KeySegmentsExtractor is allowed to place keys in categories
|
||||
// so that different parts of the key space can use different filtering
|
||||
// strategies. The following property is generally recommended for safe filter
|
||||
// applicability
|
||||
// * CATEGORY CONTIGUOUSNESS PROPERTY: each category is contiguous in
|
||||
// comparator order. In other words, any key between two keys of category c
|
||||
// must also be in category c.
|
||||
// An alternative to categories when distinct kinds of keys are interspersed
|
||||
// is to leave some segments empty when they do not apply to that key.
|
||||
// Filters are generally set up to handle an empty segment specially so that
|
||||
// it doesn't interfere with tracking accurate ranges on non-empty occurrences
|
||||
// of the segment.
|
||||
//
|
||||
// = BEYOND THE SIMPLIFIED MODEL =
|
||||
//
|
||||
// DETAILED GENERAL REQUIREMENTS (incl OTHER COMPARATORS): The exact
|
||||
// requirements on a key segments extractor depend on whether and how we use
|
||||
// filters to answer queries that they cannot answer directly. To understand
|
||||
// this, we describe
|
||||
// (A) the types of filters in terms of data they represent and can directly
|
||||
// answer queries about,
|
||||
// (B) the types of read queries that we want to use filters for, and
|
||||
// (C) the assumptions that need to be satisfied to connect those two.
|
||||
//
|
||||
// TYPES OF FILTERS: Although not exhaustive, here are some useful categories
|
||||
// of filter data:
|
||||
// * Equivalence class filtering - Represents or over-approximates a set of
|
||||
// equivalence classes on keys. The size of the representation is roughly
|
||||
// proportional to the number of equivalence classes added. Bloom and ribbon
|
||||
// filters are examples.
|
||||
// * Order-based filtering - Represents one or more subranges of a key space or
|
||||
// key segment space. A filter query only requires application of the CF
|
||||
// comparator. The size of the representation is roughly proportional to the
|
||||
// number of subranges and to the key or segment size. For example, we call a
|
||||
// simple filter representing a minimum and a maximum value for a segment a
|
||||
// min-max filter.
|
||||
//
|
||||
// TYPES OF READ QUERIES and their DIRECT FILTERS:
|
||||
// * Point query - Whether there {definitely isn't, might be} an entry for a
|
||||
// particular key in an SST file (or partition, etc.).
|
||||
// The DIRECT FILTER for a point query is an equivalence class filter on the
|
||||
// whole key.
|
||||
// * Range query - Whether there {definitely isn't, might be} any entries
|
||||
// within a lower and upper key bound, in an SST file (or partition, etc.).
|
||||
// NOTE: For this disucssion, we ignore the detail of inclusive vs.
|
||||
// exclusive bounds by assuming a generalized notion of "bound" (vs. key)
|
||||
// that conveniently represents spaces between keys. For details, see
|
||||
// https://github.com/facebook/rocksdb/pull/11434
|
||||
// The DIRECT FILTER for a range query is an order-based filter on the whole
|
||||
// key (non-empty intersection of bounds/keys). Simple minimum and maximum
|
||||
// keys for each SST file are automatically provided by metadata and used in
|
||||
// the read path for filtering (as well as binary search indexing).
|
||||
// PARTITIONING NOTE: SST metadata partitions do not have recorded minimum
|
||||
// and maximum keys, so require some special handling for range query
|
||||
// filtering. See https://github.com/facebook/rocksdb/pull/12872 etc.
|
||||
// * Where clauses - Additional constraints that can be put on range queries.
|
||||
// Specifically, a where clause is a tuple <i,j,c,b1,b2> representing that the
|
||||
// concatenated sequence of segments from i to j (inclusive) compares between
|
||||
// b1 and b2 according to comparator c.
|
||||
// EXAMPLE: To represent that segment of ordinal i is equal to s, that would
|
||||
// be <i,i,bytewise_comparator,before(s),after(s)>.
|
||||
// NOTE: To represent something like segment has a particular prefix, you
|
||||
// would need to split the key into more segments appropriately. There is
|
||||
// little loss of generality because we can combine adjacent segments for
|
||||
// specifying where clauses and implementing filters.
|
||||
// The DIRECT FILTER for a where clause is an order-based filter on the same
|
||||
// sequence of segments and comparator (non-empty intersection of bounds/keys),
|
||||
// or in the special case of an equality clause (see example), an equivalence
|
||||
// class filter on the sequence of segments.
|
||||
//
|
||||
// GENERALIZING FILTERS (INDIRECT):
|
||||
// * Point queries can utilize essentially any kind of filter by extracting
|
||||
// applicable segments of the query key (if not using whole key) and querying
|
||||
// the corresponding equivalence class or trivial range.
|
||||
// NOTE: There is NO requirement e.g. that the comparator used by the filter
|
||||
// match the CF key comparator or similar. The extractor simply needs to be
|
||||
// a pure function that does not return "out of bounds" segments.
|
||||
// FOR EXAMPLE, a min-max filter on the 4th segment of keys can also be
|
||||
// used for filtering point queries (Get/MultiGet) and could be as
|
||||
// effective and much more space efficient than a Bloom filter, depending
|
||||
// on the workload.
|
||||
//
|
||||
// Beyond point queries, we generally expect the key comparator to be a
|
||||
// lexicographic / big endian ordering at a high level (or the reverse of that
|
||||
// ordering), while each segment can use an arbitrary comparator.
|
||||
// FOR EXAMPLE, with a custom key comparator and segments extractor,
|
||||
// segment 0 could be a 4-byte unsigned little-endian integer,
|
||||
// segment 1 could be an 8-byte signed big-endian integer. This framework
|
||||
// requires segment 0 to come before segment 1 in the key and to take
|
||||
// precedence in key ordering (i.e. segment 1 order is only consulted when
|
||||
// keys are equal in segment 0).
|
||||
//
|
||||
// * Equivalence class filters can apply to range queries under conditions
|
||||
// resembling legacy prefix filtering (prefix_extractor). An equivalence class
|
||||
// filter on segments i through j and category set s is applicable to a range
|
||||
// query from lb to ub if
|
||||
// * All segments through j extracted from lb and ub are equal.
|
||||
// NOTE: being in the same filtering equivalence class is insufficient, as
|
||||
// that could be unrelated inputs with a hash collision. Here we are
|
||||
// omitting details that would formally accommodate comparators in which
|
||||
// different bytes can be considered equal.
|
||||
// * The categories of lb and ub are in the category set s.
|
||||
// * COMMON SEGMENT PREFIX PROPERTY (for all x, y, z; params j, s): if
|
||||
// * Keys x and z have equal segments up through ordinal j, and
|
||||
// * Keys x and z are in categories in category set s, and
|
||||
// * Key y is ordered x < y < z according to the CF comparator,
|
||||
// then both
|
||||
// * Key y has equal segments up through ordinal j (compared to x and z)
|
||||
// * Key y is in a category in category set s
|
||||
// (This is implied by the SEGMENT MAXIMAL PREFIX PROPERTY in the simplified
|
||||
// model.)
|
||||
//
|
||||
// * Order-based filters on segments (rather than whole key) can apply to range
|
||||
// queries (with "whole key" bounds). Specifically, an order-based filter on
|
||||
// segments i through j and category set s is applicable to a range query from
|
||||
// lb to ub if
|
||||
// * All segments through i-1 extracted from lb and ub are equal
|
||||
// * The categories of lb and ub are in the category set s.
|
||||
// * SEGMENT ORDERING PROPERTY for ordinal i through j, segments
|
||||
// comparator c, category set s, for all x, y, and z: if
|
||||
// * Keys x and z have equal segments up through ordinal i-1, and
|
||||
// * Keys x and z are in categories in category set s, and
|
||||
// * Key y is ordered x < y < z according to the CF comparator,
|
||||
// then both
|
||||
// * The common segment prefix property is satisifed through ordinal i-1
|
||||
// and with category set s
|
||||
// * x_i..j <= y_i..j <= z_i..j according to segment comparator c, where
|
||||
// x_i..j is the concatenation of segments i through j of key x (etc.).
|
||||
// (This is implied by the SEGMENT MAXIMAL PREFIX PROPERTY in the simplified
|
||||
// model.)
|
||||
//
|
||||
// INTERESTING EXAMPLES:
|
||||
// Consider a segment encoding called BadVarInt1 in which a byte with
|
||||
// highest-order bit 1 means "start a new segment". Also consider BadVarInt0
|
||||
// which starts a new segment on highest-order bit 0.
|
||||
//
|
||||
// Configuration: bytewise comp, BadVarInt1 format for segments 0-3 with
|
||||
// segment 3 also continuing to the end of the key
|
||||
// x = 0x 20 21|82 23|||
|
||||
// y = 0x 20 21|82 23 24|85||
|
||||
// z = 0x 20 21|82 23|84 25||
|
||||
//
|
||||
// For i=j=1, this set of keys violate the common segment prefix property and
|
||||
// segment ordering property, so can lead to incorrect equivalence class
|
||||
// filtering or order-based filtering.
|
||||
//
|
||||
// Suppose we modify the configuration so that "short" keys (empty in segment
|
||||
// 2) are placed in an unfiltered category. In that case, x above doesn't meet
|
||||
// the precondition for being limited by segment properties. Consider these
|
||||
// keys instead:
|
||||
// x = 0x 20 21|82 23 24|85||
|
||||
// y = 0x 20 21|82 23 24|85 26|87|
|
||||
// z = 0x 20 21|82 23 24|85|86|
|
||||
// m = 0x 20 21|82 23 25|85|86|
|
||||
// n = 0x 20 21|82 23|84 25||
|
||||
//
|
||||
// Although segment 1 values might be out of order with key order,
|
||||
// re-categorizing the short keys has allowed satisfying the common segment
|
||||
// prefix property with j=1 (and with j=0), so we can use equivalence class
|
||||
// filters on segment 1, or 0, or 0 to 1. However, violation of the segment
|
||||
// ordering property on i=j=1 (see z, m, n) means we can't use order-based.
|
||||
//
|
||||
// p = 0x 20 21|82 23|84 25 26||
|
||||
// q = 0x 20 21|82 23|84 25|86|
|
||||
//
|
||||
// But keys can still be short from segment 2 to 3, and thus we are violating
|
||||
// the common segment prefix property for segment 2 (see n, p, q).
|
||||
//
|
||||
// Configuration: bytewise comp, BadVarInt0 format for segments 0-3 with
|
||||
// segment 3 also continuing to the end of the key. No short key category.
|
||||
// x = 0x 80 81|22 83|||
|
||||
// y = 0x 80 81|22 83|24 85||
|
||||
// z = 0x 80 81|22 83 84|25||
|
||||
// m = 0x 80 82|22 83|||
|
||||
// n = 0x 80 83|22 84|24 85||
|
||||
//
|
||||
// Even though this violates the segment maximal prefix property of the
|
||||
// simplified model, the common segment prefix property and segment ordering
|
||||
// property are satisfied for the various segment ordinals. In broader terms,
|
||||
// the usual rule of the delimiter going with the segment before it can be
|
||||
// violated if every byte value below some threshold starts a segment. (This
|
||||
// has not been formally verified and is not recommended.)
|
||||
//
|
||||
// Suppose that we are paranoid, however, and decide to place short keys
|
||||
// (empty in segment 2) into an unfiltered category. This is potentially a
|
||||
// dangerous decision because loss of continuity at least affects the
|
||||
// ability to filter on segment 0 (common segment prefix property violated
|
||||
// with i=j=0; see z, m, n; m not in category set). Thus, excluding short keys
|
||||
// with categories is not a recommended solution either.
|
||||
class KeySegmentsExtractor {
|
||||
public:
|
||||
// The extractor assigns keys to categories so that it is easier to
|
||||
@@ -269,6 +461,14 @@ class KeySegmentsExtractor {
|
||||
Result* result) const = 0;
|
||||
};
|
||||
|
||||
// Constructs a KeySegmentsExtractor for fixed-width key segments that safely
|
||||
// handles short keys by truncating segments at the end of the input key.
|
||||
// See comments on KeySegmentsExtractor for why this is much safer for
|
||||
// filtering than "all or nothing" fixed-size segments. This is essentially
|
||||
// a generalization of (New)CappedPrefixTransform.
|
||||
std::shared_ptr<const KeySegmentsExtractor>
|
||||
MakeSharedCappedKeySegmentsExtractor(const std::vector<size_t>& byte_widths);
|
||||
|
||||
// Alternatives for filtering inputs
|
||||
|
||||
// An individual key segment.
|
||||
@@ -305,13 +505,13 @@ struct SelectUserTimestamp {};
|
||||
|
||||
struct SelectColumnName {};
|
||||
|
||||
struct SelectValue {};
|
||||
|
||||
// Note: more variants might be added in the future.
|
||||
// NOTE: more variants might be added in the future.
|
||||
// NOTE2: filtering on values is not supported because it could easily break
|
||||
// overwrite semantics. (Filter out SST with newer, non-matching value but
|
||||
// see obsolete value that does match.)
|
||||
using FilterInput =
|
||||
std::variant<SelectWholeKey, SelectKeySegment, SelectKeySegmentRange,
|
||||
SelectLegacyKeyPrefix, SelectUserTimestamp, SelectColumnName,
|
||||
SelectValue>;
|
||||
SelectLegacyKeyPrefix, SelectUserTimestamp, SelectColumnName>;
|
||||
|
||||
// Base class for individual filtering schemes in terms of chosen
|
||||
// FilterInputs, but not tied to a particular KeySegmentsExtractor.
|
||||
@@ -336,6 +536,10 @@ std::shared_ptr<SstQueryFilterConfig> MakeSharedBytewiseMinMaxSQFC(
|
||||
FilterInput select, KeySegmentsExtractor::KeyCategorySet categories =
|
||||
KeySegmentsExtractor::KeyCategorySet::All());
|
||||
|
||||
std::shared_ptr<SstQueryFilterConfig> MakeSharedReverseBytewiseMinMaxSQFC(
|
||||
FilterInput select, KeySegmentsExtractor::KeyCategorySet categories =
|
||||
KeySegmentsExtractor::KeyCategorySet::All());
|
||||
|
||||
// TODO: more kinds of filters, eventually including Bloom/ribbon filters
|
||||
// and replacing the old filter configuration APIs
|
||||
|
||||
|
||||
+68
-36
@@ -466,14 +466,27 @@ struct CompactionServiceJobInfo {
|
||||
|
||||
Env::Priority priority;
|
||||
|
||||
// Additional Compaction Details that can be useful in the CompactionService
|
||||
CompactionReason compaction_reason;
|
||||
bool is_full_compaction;
|
||||
bool is_manual_compaction;
|
||||
bool bottommost_level;
|
||||
|
||||
CompactionServiceJobInfo(std::string db_name_, std::string db_id_,
|
||||
std::string db_session_id_, uint64_t job_id_,
|
||||
Env::Priority priority_)
|
||||
Env::Priority priority_,
|
||||
CompactionReason compaction_reason_,
|
||||
bool is_full_compaction_, bool is_manual_compaction_,
|
||||
bool bottommost_level_)
|
||||
: db_name(std::move(db_name_)),
|
||||
db_id(std::move(db_id_)),
|
||||
db_session_id(std::move(db_session_id_)),
|
||||
job_id(job_id_),
|
||||
priority(priority_) {}
|
||||
priority(priority_),
|
||||
compaction_reason(compaction_reason_),
|
||||
is_full_compaction(is_full_compaction_),
|
||||
is_manual_compaction(is_manual_compaction_),
|
||||
bottommost_level(bottommost_level_) {}
|
||||
};
|
||||
|
||||
struct CompactionServiceScheduleResponse {
|
||||
@@ -1380,16 +1393,35 @@ struct DBOptions {
|
||||
// ReadOptions::background_purge_on_iterator_cleanup.
|
||||
bool avoid_unnecessary_blocking_io = false;
|
||||
|
||||
// Historically DB ID has always been stored in Identity File in DB folder.
|
||||
// If this flag is true, the DB ID is written to Manifest file in addition
|
||||
// to the Identity file. By doing this 2 problems are solved
|
||||
// 1. We don't checksum the Identity file where as Manifest file is.
|
||||
// 2. Since the source of truth for DB is Manifest file DB ID will sit with
|
||||
// the source of truth. Previously the Identity file could be copied
|
||||
// independent of Manifest and that can result in wrong DB ID.
|
||||
// We recommend setting this flag to true.
|
||||
// Default: false
|
||||
bool write_dbid_to_manifest = false;
|
||||
// The DB unique ID can be saved in the DB manifest (preferred, this option)
|
||||
// or an IDENTITY file (historical, deprecated), or both. If this option is
|
||||
// set to false (old behavior), then write_identity_file must be set to true.
|
||||
// The manifest is preferred because
|
||||
// 1. The IDENTITY file is not checksummed, so it is not as safe against
|
||||
// corruption.
|
||||
// 2. The IDENTITY file may or may not be copied with the DB (e.g. not
|
||||
// copied by BackupEngine), so is not reliable for the provenance of a DB.
|
||||
// This option might eventually be obsolete and removed as Identity files
|
||||
// are phased out.
|
||||
bool write_dbid_to_manifest = true;
|
||||
|
||||
// It is expected that the Identity file will be obsoleted by recording
|
||||
// DB ID in the manifest (see write_dbid_to_manifest). Setting this to true
|
||||
// maintains the historical behavior of writing an Identity file, while
|
||||
// setting to false is expected to be the future default. This option might
|
||||
// eventually be obsolete and removed as Identity files are phased out.
|
||||
bool write_identity_file = true;
|
||||
|
||||
// Historically, when prefix_extractor != nullptr, iterators have an
|
||||
// unfortunate default semantics of *possibly* only returning data
|
||||
// within the same prefix. To avoid "spooky action at a distance," iterator
|
||||
// bounds should come from the instantiation or seeking of the iterator,
|
||||
// not from a mutable column family option.
|
||||
//
|
||||
// When set to true, it is as if every iterator is created with
|
||||
// total_order_seek=true and only auto_prefix_mode=true and
|
||||
// prefix_same_as_start=true can take advantage of prefix seek optimizations.
|
||||
bool prefix_seek_opt_in_only = false;
|
||||
|
||||
// The number of bytes to prefetch when reading the log. This is mostly useful
|
||||
// for reading a remotely located log, as it can save the number of
|
||||
@@ -1840,10 +1872,10 @@ struct ReadOptions {
|
||||
bool auto_prefix_mode = false;
|
||||
|
||||
// Enforce that the iterator only iterates over the same prefix as the seek.
|
||||
// This option is effective only for prefix seeks, i.e. prefix_extractor is
|
||||
// non-null for the column family and total_order_seek is false. Unlike
|
||||
// iterate_upper_bound, prefix_same_as_start only works within a prefix
|
||||
// but in both directions.
|
||||
// This makes the iterator bounds dependent on the column family's current
|
||||
// prefix_extractor, which is mutable. When SST files have been built with
|
||||
// the same prefix extractor, prefix filtering optimizations will be used
|
||||
// for both Seek and SeekForPrev.
|
||||
bool prefix_same_as_start = false;
|
||||
|
||||
// Keep the blocks loaded by the iterator pinned in memory as long as the
|
||||
@@ -2131,8 +2163,16 @@ struct CompactRangeOptions {
|
||||
// IngestExternalFileOptions is used by IngestExternalFile()
|
||||
struct IngestExternalFileOptions {
|
||||
// Can be set to true to move the files instead of copying them.
|
||||
// The input files will be unlinked after successful ingestion.
|
||||
// The implementation depends on hard links (LinkFile) instead of traditional
|
||||
// move (RenameFile) to maximize the chances to restore to the original
|
||||
// state upon failure.
|
||||
bool move_files = false;
|
||||
// If set to true, ingestion falls back to copy when move fails.
|
||||
// Same as move_files except that input files will NOT be unlinked.
|
||||
// Only one of `move_files` and `link_files` can be set at the same time.
|
||||
bool link_files = false;
|
||||
// If set to true, ingestion falls back to copy when hard linking fails.
|
||||
// This applies to both `move_files` and `link_files`.
|
||||
bool failed_move_fall_back_to_copy = true;
|
||||
// If set to false, an ingested file keys could appear in existing snapshots
|
||||
// that where created before the file was ingested.
|
||||
@@ -2204,22 +2244,17 @@ struct IngestExternalFileOptions {
|
||||
// XXX: "bottommost" is obsolete/confusing terminology to refer to last level
|
||||
bool fail_if_not_bottommost_level = false;
|
||||
// EXPERIMENTAL
|
||||
// If set to true, ingestion will
|
||||
// - allow the files to not be generated by SstFileWriter, and
|
||||
// - ignore cf_id mismatch between cf_id in the files and the CF they are
|
||||
// being ingested into.
|
||||
//
|
||||
// REQUIRES:
|
||||
// - files to be ingested do not overlap with existing keys.
|
||||
// - write_global_seqno = false
|
||||
// - move_files = false
|
||||
//
|
||||
// Warning: This ONLY works for SST files where all keys have sequence number
|
||||
// zero and with no duplicated user keys (this should be guaranteed if the
|
||||
// file is generated by a DB with zero as the largest sequence number).
|
||||
// We scan the entire SST files to validate sequence numbers.
|
||||
// Warning: If a DB contains ingested files generated by another DB/CF,
|
||||
// RepairDB() may not correctly recover these files. It may lose these files.
|
||||
// Enables ingestion of files not generated by SstFileWriter. When true:
|
||||
// - Allows files to be ingested when their cf_id doesn't match the CF they
|
||||
// are being ingested into.
|
||||
// REQUIREMENTS:
|
||||
// - Ingested files must not overlap with existing keys.
|
||||
// - `write_global_seqno` must be false.
|
||||
// - All keys in ingested files should have sequence number 0. We fail
|
||||
// ingestion if any sequence numbers is non-zero.
|
||||
// WARNING: If a DB contains ingested files generated by another DB/CF,
|
||||
// RepairDB() may not recover these files correctly, potentially leading to
|
||||
// data loss.
|
||||
bool allow_db_generated_files = false;
|
||||
};
|
||||
|
||||
@@ -2284,9 +2319,6 @@ struct SizeApproximationOptions {
|
||||
};
|
||||
|
||||
struct CompactionServiceOptionsOverride {
|
||||
// Currently pointer configurations are not passed to compaction service
|
||||
// compaction so the user needs to set it. It will be removed once pointer
|
||||
// configuration passing is supported.
|
||||
Env* env = Env::Default();
|
||||
std::shared_ptr<FileChecksumGenFactory> file_checksum_gen_factory = nullptr;
|
||||
|
||||
|
||||
@@ -74,6 +74,7 @@ struct TablePropertiesNames {
|
||||
static const std::string kSequenceNumberTimeMapping;
|
||||
static const std::string kTailStartOffset;
|
||||
static const std::string kUserDefinedTimestampsPersisted;
|
||||
static const std::string kKeyLargestSeqno;
|
||||
};
|
||||
|
||||
// `TablePropertiesCollector` provides the mechanism for users to collect
|
||||
@@ -134,6 +135,7 @@ class TablePropertiesCollector {
|
||||
|
||||
// Return the human-readable properties, where the key is property name and
|
||||
// the value is the human-readable form of value.
|
||||
// Returned properties are used for logging.
|
||||
// It will only be called after Finish() has been called by RocksDB internal.
|
||||
virtual UserCollectedProperties GetReadableProperties() const = 0;
|
||||
|
||||
@@ -292,6 +294,12 @@ struct TableProperties {
|
||||
// it's explicitly written to meta properties block.
|
||||
uint64_t user_defined_timestamps_persisted = 1;
|
||||
|
||||
// The largest sequence number of keys in this file.
|
||||
// UINT64_MAX means unknown.
|
||||
// Only written to properties block if known (should be known unless the
|
||||
// table is empty).
|
||||
uint64_t key_largest_seqno = UINT64_MAX;
|
||||
|
||||
// DB identity
|
||||
// db_id is an identifier generated the first time the DB is created
|
||||
// If DB identity is unset or unassigned, `db_id` will be an empty string.
|
||||
|
||||
@@ -74,6 +74,7 @@ class LDBCommand {
|
||||
static const std::string ARG_DECODE_BLOB_INDEX;
|
||||
static const std::string ARG_DUMP_UNCOMPRESSED_BLOBS;
|
||||
static const std::string ARG_READ_TIMESTAMP;
|
||||
static const std::string ARG_GET_WRITE_UNIX_TIME;
|
||||
|
||||
struct ParsedParams {
|
||||
std::string cmd;
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
// NOTE: in 'main' development branch, this should be the *next*
|
||||
// minor or major version number planned for release.
|
||||
#define ROCKSDB_MAJOR 9
|
||||
#define ROCKSDB_MINOR 6
|
||||
#define ROCKSDB_MINOR 7
|
||||
#define ROCKSDB_PATCH 2
|
||||
|
||||
// Do not use these. We made the mistake of declaring macros starting with
|
||||
|
||||
@@ -60,7 +60,8 @@ jbyteArray Java_org_rocksdb_WriteBatchTest_getContents(JNIEnv* env,
|
||||
ROCKSDB_NAMESPACE::Arena arena;
|
||||
ROCKSDB_NAMESPACE::ScopedArenaPtr<ROCKSDB_NAMESPACE::InternalIterator> iter(
|
||||
mem->NewIterator(ROCKSDB_NAMESPACE::ReadOptions(),
|
||||
/*seqno_to_time_mapping=*/nullptr, &arena));
|
||||
/*seqno_to_time_mapping=*/nullptr, &arena,
|
||||
/*prefix_extractor=*/nullptr));
|
||||
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
|
||||
ROCKSDB_NAMESPACE::ParsedInternalKey ikey;
|
||||
ikey.clear();
|
||||
|
||||
@@ -733,11 +733,10 @@ public interface AdvancedMutableColumnFamilyOptionsInterface<
|
||||
double blobGarbageCollectionAgeCutoff();
|
||||
|
||||
/**
|
||||
* If the ratio of garbage in the oldest blob files exceeds this threshold,
|
||||
* targeted compactions are scheduled in order to force garbage collecting
|
||||
* the blob files in question, assuming they are all eligible based on the
|
||||
* value of {@link #blobGarbageCollectionAgeCutoff} above. This option is
|
||||
* currently only supported with leveled compactions.
|
||||
* If the ratio of garbage in the blob files currently eligible for garbage
|
||||
* collection exceeds this threshold, targeted compactions are scheduled in
|
||||
* order to force garbage collecting the oldest blob files. This option is
|
||||
* currently only supported with leveled compactions.
|
||||
* <p>
|
||||
* Note that {@link #enableBlobGarbageCollection} has to be set in order for this
|
||||
* option to have any effect.
|
||||
|
||||
Loaded 100 of 135 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user