mirror of
https://github.com/facebook/rocksdb.git
synced 2026-11-07 11:32:34 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
44e95d8af5 | ||
|
|
778f25fbbd | ||
|
|
1b497c95b3 | ||
|
|
73be33ca2e | ||
|
|
139bfd028c | ||
|
|
08ed842be3 | ||
|
|
026659f496 | ||
|
|
7d2fe503e8 | ||
|
|
b569ba71f4 | ||
|
|
23f7cf19d7 | ||
|
|
3690ee36c1 | ||
|
|
4c3b3e7a37 | ||
|
|
f692402788 | ||
|
|
48f3a8085f | ||
|
|
3230214cc3 | ||
|
|
57a8e69d4e | ||
|
|
3fd1f11d35 | ||
|
|
9c94559de7 | ||
|
|
8b38d4b400 | ||
|
|
c0be6a4b90 | ||
|
|
0ca691654f | ||
|
|
58fc9d61b7 | ||
|
|
ac24f152a1 | ||
|
|
a44f4b8653 | ||
|
|
f22557886e | ||
|
|
8ad4c7efc4 | ||
|
|
787730c859 | ||
|
|
ecc084d301 | ||
|
|
da5e11310b | ||
|
|
55de26580a | ||
|
|
2cb00c6921 | ||
|
|
2abbb02d14 | ||
|
|
cbebbad7d9 | ||
|
|
dd76862b00 | ||
|
|
351d2fd2b6 | ||
|
|
8592517c89 | ||
|
|
f7237e3395 | ||
|
|
a571cbed17 | ||
|
|
2c9aa69a93 | ||
|
|
fe6c8cb1d6 | ||
|
|
632746bb5b | ||
|
|
8181dfb1c4 | ||
|
|
d963661d6f | ||
|
|
263fa15b44 | ||
|
|
bceb2dfe6a | ||
|
|
32dd657bad | ||
|
|
917e98ff9e | ||
|
|
12960f0b57 | ||
|
|
9375c3b635 | ||
|
|
dd23e84cad | ||
|
|
389e66bef5 | ||
|
|
2c2776f1f3 | ||
|
|
79790cf2a8 | ||
|
|
22105366d9 | ||
|
|
d327d56081 | ||
|
|
d02f63cc54 | ||
|
|
2a5ff78c12 | ||
|
|
fbbb08770f |
No files matched your search
+1
-1
@@ -32,7 +32,7 @@
|
||||
# 3. cmake ..
|
||||
# 4. make -j
|
||||
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
cmake_minimum_required(VERSION 3.12)
|
||||
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake/modules/")
|
||||
include(ReadVersion)
|
||||
|
||||
+65
@@ -1,6 +1,71 @@
|
||||
# Rocksdb Change Log
|
||||
> NOTE: Entries for next release do not go here. Follow instructions in `unreleased_history/README.txt`
|
||||
|
||||
## 9.8.4 (11/18/2024)
|
||||
### Behavior Changes
|
||||
* When Remote Compaction is enabled, do not purge OPTIONS file immediately by DeleteObsoleteOptionsFiles() after SetOptions(). Rely on PurgeObsoleteFiles() to clean up obsolete OPTIONS file after each compaction.
|
||||
|
||||
## 9.8.3 (11/12/2024)
|
||||
### Bug Fixes
|
||||
* Fix missing cases of corruption retry during DB open and read API processing.
|
||||
|
||||
## 9.8.2 (11/06/2024)
|
||||
### Public API Changes
|
||||
* Added a new API `Transaction::GetAttributeGroupIterator` that can be used to create a multi-column-family attribute group iterator over the specified column families, including the data from both the transaction and the underlying database. This API is currently supported for optimistic and write-committed pessimistic transactions.
|
||||
|
||||
### Behavior Changes
|
||||
* `BaseDeltaIterator` now honors the read option `allow_unprepared_value`.
|
||||
|
||||
### Bug Fixes
|
||||
* `BaseDeltaIterator` now calls `PrepareValue` on the base iterator in case it has been created with the `allow_unprepared_value` read option set. Earlier, such base iterators could lead to incorrect values being exposed from `BaseDeltaIterator`.
|
||||
* Fix a bug for replaying WALs for WriteCommitted transaction DB when its user-defined timestamps setting is toggled on/off between DB sessions.
|
||||
|
||||
## 9.8.1 (10/31/2024)
|
||||
### Bug Fixes
|
||||
* Fix a leak of obsolete blob files left open until DB::Close(). This bug was introduced in version 9.4.0.
|
||||
|
||||
## 9.8.0 (10/25/2024)
|
||||
### New Features
|
||||
* All non-`block_cache` options in `BlockBasedTableOptions` are now mutable with `DB::SetOptions()`. See also Bug Fixes below.
|
||||
* When using iterators with BlobDB, it is now possible to load large values on an on-demand basis, i.e. only if they are actually needed by the application. This can save I/O in use cases where the values associated with certain keys are not needed. For more details, see the new read option `allow_unprepared_value` and the iterator API `PrepareValue`.
|
||||
* Add a new file ingestion option `IngestExternalFileOptions::fill_cache` to support not adding blocks from ingested files into block cache during file ingestion.
|
||||
* The option `allow_unprepared_value` is now also supported for multi-column-family iterators (i.e. `CoalescingIterator` and `AttributeGroupIterator`).
|
||||
* When a file with just one range deletion (standalone range deletion file) is ingested via bulk loading, it will be marked for compaction. During compaction, this type of files can be used to directly filter out some input files that are not protected by any snapshots and completely deleted by the standalone range deletion file.
|
||||
|
||||
### Behavior Changes
|
||||
* During file ingestion, overlapping files level assignment are done in multiple batches, so that they can potentially be assigned to lower levels other than always land on L0.
|
||||
* OPTIONS file to be loaded by remote worker is now preserved so that it does not get purged by the primary host. A similar technique as how we are preserving new SST files from getting purged is used for this. min_options_file_numbers_ is tracked like pending_outputs_ is tracked.
|
||||
* Trim readahead_size during scans so data blocks containing keys that are not in the same prefix as the seek key in `Seek()` are not prefetched when `ReadOptions::auto_readahead_size=true` (default value) and `ReadOptions::prefix_same_as_start = true`
|
||||
* Assigning levels for external files are done in the same way for universal compaction and leveled compaction. The old behavior tends to assign files to L0 while the new behavior will assign the files to the lowest level possible.
|
||||
|
||||
### Bug Fixes
|
||||
* Fix a longstanding race condition in SetOptions for `block_based_table_factory` options. The fix has some subtle behavior changes because of copying and replacing the TableFactory on a change with SetOptions, including requiring an Iterator::Refresh() for an existing Iterator to use the latest options.
|
||||
* 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.
|
||||
* `GetApproximateMemTableStats()` could return disastrously bad estimates 5-25% of the time. The function has been re-engineered to return much better estimates with similar CPU cost.
|
||||
* Skip insertion of compressed blocks in the secondary cache if the lowest_used_cache_tier DB option is kVolatileTier.
|
||||
* Fix an issue in level compaction where a small CF with small compaction debt can cause the DB to allow parallel compactions. (#13054)
|
||||
* 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`
|
||||
|
||||
## 9.7.0 (09/20/2024)
|
||||
### New Features
|
||||
* Make Cache a customizable class that can be instantiated by the object registry.
|
||||
* Add new option `prefix_seek_opt_in_only` that makes iterators generally safer when you might set a `prefix_extractor`. When `prefix_seek_opt_in_only=true`, which is expected to be the future default, prefix seek is only used when `prefix_same_as_start` or `auto_prefix_mode` are set. Also, `prefix_same_as_start` and `auto_prefix_mode` now allow prefix filtering even with `total_order_seek=true`.
|
||||
* Add a new table property "rocksdb.key.largest.seqno" which records the largest sequence number of all keys in file. It is verified to be zero during SST file ingestion.
|
||||
|
||||
### Behavior Changes
|
||||
* Changed the semantics of the BlobDB configuration option `blob_garbage_collection_force_threshold` to define a threshold for the overall garbage ratio of all blob files currently eligible for garbage collection (according to `blob_garbage_collection_age_cutoff`). This can provide better control over space amplification at the cost of slightly higher write amplification.
|
||||
* Set `write_dbid_to_manifest=true` by default. This means DB ID will now be preserved through backups, checkpoints, etc. by default. Also add `write_identity_file` option which can be set to false for anticipated future behavior.
|
||||
* In FIFO compaction, compactions for changing file temperature (configured by option `file_temperature_age_thresholds`) will compact one file at a time, instead of merging multiple eligible file together (#13018).
|
||||
* Support ingesting db generated files using hard link, i.e. IngestExternalFileOptions::move_files/link_files and IngestExternalFileOptions::allow_db_generated_files.
|
||||
* Add a new file ingestion option `IngestExternalFileOptions::link_files` to hard link input files and preserve original files links after ingestion.
|
||||
* DB::Close now untracks files in SstFileManager, making avaialble any space used
|
||||
by them. Prior to this change they would be orphaned until the DB is re-opened.
|
||||
|
||||
### Bug Fixes
|
||||
* Fix a bug in CompactRange() where result files may not be compacted in any future compaction. This can only happen when users configure CompactRangeOptions::change_level to true and the change level step of manual compaction fails (#13009).
|
||||
* Fix handling of dynamic change of `prefix_extractor` with memtable prefix filter. Previously, prefix seek could mix different prefix interpretations between memtable and SST files. Now the latest `prefix_extractor` at the time of iterator creation or refresh is respected.
|
||||
* Fix a bug with manual_wal_flush and auto error recovery from WAL failure that may cause CFs to be inconsistent (#12995). The fix will set potential WAL write failure as fatal error when manual_wal_flush is true, and disables auto error recovery from these errors.
|
||||
|
||||
## 9.6.0 (08/19/2024)
|
||||
### New Features
|
||||
* *Best efforts recovery supports recovering to incomplete Version with a clean seqno cut that presents a valid point in time view from the user's perspective, if versioning history doesn't include atomic flush.
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||
|
||||
try:
|
||||
from builtins import str
|
||||
@@ -132,7 +131,7 @@ def generate_targets(repo_path, deps_map):
|
||||
if len(sys.argv) >= 2:
|
||||
# Heuristically quote and canonicalize whitespace for inclusion
|
||||
# in how the file was generated.
|
||||
extra_argv = " '{0}'".format(" ".join(sys.argv[1].split()))
|
||||
extra_argv = " '{}'".format(" ".join(sys.argv[1].split()))
|
||||
|
||||
TARGETS = TARGETSBuilder("%s/TARGETS" % repo_path, extra_argv)
|
||||
|
||||
@@ -213,7 +212,7 @@ def generate_targets(repo_path, deps_map):
|
||||
for src in src_mk.get("MICROBENCH_SOURCES", []):
|
||||
name = src.rsplit("/", 1)[1].split(".")[0] if "/" in src else src.split(".")[0]
|
||||
TARGETS.add_binary(name, [src], [], extra_bench_libs=True)
|
||||
print("Extra dependencies:\n{0}".format(json.dumps(deps_map)))
|
||||
print(f"Extra dependencies:\n{json.dumps(deps_map)}")
|
||||
|
||||
# Dictionary test executable name -> relative source file path
|
||||
test_source_map = {}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||
|
||||
try:
|
||||
from builtins import object, str
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||
|
||||
rocksdb_target_header_template = """# This file \100generated by:
|
||||
#$ python3 buckifier/buckify_rocksdb.py{extra_argv}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
"""
|
||||
This module keeps commonly used components.
|
||||
"""
|
||||
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||
|
||||
try:
|
||||
from builtins import object
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
#
|
||||
# The solution is to move the include out of the #ifdef.
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import argparse
|
||||
import re
|
||||
@@ -62,7 +61,7 @@ def expand_include(
|
||||
|
||||
included.add(include_path)
|
||||
with open(include_path) as f:
|
||||
print('#line 1 "{}"'.format(include_path), file=source_out)
|
||||
print(f'#line 1 "{include_path}"', file=source_out)
|
||||
process_file(
|
||||
f, include_path, source_out, header_out, include_paths, public_include_paths
|
||||
)
|
||||
@@ -118,7 +117,7 @@ def process_file(
|
||||
)
|
||||
|
||||
if expanded:
|
||||
print('#line {} "{}"'.format(line + 1, abs_path), file=source_out)
|
||||
print(f'#line {line + 1} "{abs_path}"', file=source_out)
|
||||
elif text != "#pragma once\n":
|
||||
source_out.write(text)
|
||||
|
||||
@@ -157,8 +156,8 @@ def main():
|
||||
with open(filename) as f, open(args.source_out, "w") as source_out, open(
|
||||
args.header_out, "w"
|
||||
) as header_out:
|
||||
print('#line 1 "{}"'.format(filename), file=source_out)
|
||||
print('#include "{}"'.format(header_out.name), file=source_out)
|
||||
print(f'#line 1 "{filename}"', file=source_out)
|
||||
print(f'#include "{header_out.name}"', file=source_out)
|
||||
process_file(
|
||||
f, abs_path, source_out, header_out, include_paths, public_include_paths
|
||||
)
|
||||
|
||||
@@ -102,7 +102,7 @@ class BenchmarkUtils:
|
||||
|
||||
|
||||
class ResultParser:
|
||||
def __init__(self, field="(\w|[+-:.%])+", intrafield="(\s)+", separator="\t"):
|
||||
def __init__(self, field=r"(\w|[+-:.%])+", intrafield=r"(\s)+", separator="\t"):
|
||||
self.field = re.compile(field)
|
||||
self.intra = re.compile(intrafield)
|
||||
self.sep = re.compile(separator)
|
||||
@@ -159,7 +159,7 @@ class ResultParser:
|
||||
|
||||
|
||||
def load_report_from_tsv(filename: str):
|
||||
file = open(filename, "r")
|
||||
file = open(filename)
|
||||
contents = file.readlines()
|
||||
file.close()
|
||||
parser = ResultParser()
|
||||
|
||||
+10
-11
@@ -9,7 +9,6 @@
|
||||
- Prints those error messages to stdout
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||
|
||||
import re
|
||||
import sys
|
||||
@@ -43,7 +42,7 @@ class GTestErrorParser(ErrorParserBase):
|
||||
return None
|
||||
gtest_fail_match = self._GTEST_FAIL_PATTERN.match(line)
|
||||
if gtest_fail_match:
|
||||
return "%s failed: %s" % (self._last_gtest_name, gtest_fail_match.group(1))
|
||||
return "{} failed: {}".format(self._last_gtest_name, gtest_fail_match.group(1))
|
||||
return None
|
||||
|
||||
|
||||
@@ -66,52 +65,52 @@ class CompilerErrorParser(MatchErrorParser):
|
||||
# format (link error):
|
||||
# '<filename>:<line #>: error: <error msg>'
|
||||
# The below regex catches both
|
||||
super(CompilerErrorParser, self).__init__(r"\S+:\d+: error:")
|
||||
super().__init__(r"\S+:\d+: error:")
|
||||
|
||||
|
||||
class ScanBuildErrorParser(MatchErrorParser):
|
||||
def __init__(self):
|
||||
super(ScanBuildErrorParser, self).__init__(r"scan-build: \d+ bugs found.$")
|
||||
super().__init__(r"scan-build: \d+ bugs found.$")
|
||||
|
||||
|
||||
class DbCrashErrorParser(MatchErrorParser):
|
||||
def __init__(self):
|
||||
super(DbCrashErrorParser, self).__init__(r"\*\*\*.*\^$|TEST FAILED.")
|
||||
super().__init__(r"\*\*\*.*\^$|TEST FAILED.")
|
||||
|
||||
|
||||
class WriteStressErrorParser(MatchErrorParser):
|
||||
def __init__(self):
|
||||
super(WriteStressErrorParser, self).__init__(
|
||||
super().__init__(
|
||||
r"ERROR: write_stress died with exitcode=\d+"
|
||||
)
|
||||
|
||||
|
||||
class AsanErrorParser(MatchErrorParser):
|
||||
def __init__(self):
|
||||
super(AsanErrorParser, self).__init__(r"==\d+==ERROR: AddressSanitizer:")
|
||||
super().__init__(r"==\d+==ERROR: AddressSanitizer:")
|
||||
|
||||
|
||||
class UbsanErrorParser(MatchErrorParser):
|
||||
def __init__(self):
|
||||
# format: '<filename>:<line #>:<column #>: runtime error: <error msg>'
|
||||
super(UbsanErrorParser, self).__init__(r"\S+:\d+:\d+: runtime error:")
|
||||
super().__init__(r"\S+:\d+:\d+: runtime error:")
|
||||
|
||||
|
||||
class ValgrindErrorParser(MatchErrorParser):
|
||||
def __init__(self):
|
||||
# just grab the summary, valgrind doesn't clearly distinguish errors
|
||||
# from other log messages.
|
||||
super(ValgrindErrorParser, self).__init__(r"==\d+== ERROR SUMMARY:")
|
||||
super().__init__(r"==\d+== ERROR SUMMARY:")
|
||||
|
||||
|
||||
class CompatErrorParser(MatchErrorParser):
|
||||
def __init__(self):
|
||||
super(CompatErrorParser, self).__init__(r"==== .*[Ee]rror.* ====$")
|
||||
super().__init__(r"==== .*[Ee]rror.* ====$")
|
||||
|
||||
|
||||
class TsanErrorParser(MatchErrorParser):
|
||||
def __init__(self):
|
||||
super(TsanErrorParser, self).__init__(r"WARNING: ThreadSanitizer:")
|
||||
super().__init__(r"WARNING: ThreadSanitizer:")
|
||||
|
||||
|
||||
_TEST_NAME_TO_PARSERS = {
|
||||
|
||||
Vendored
+3
-1
@@ -133,7 +133,9 @@ 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) {
|
||||
if (StartsWith(value, "null")) {
|
||||
cache = nullptr;
|
||||
} else if (value.find("://") == std::string::npos) {
|
||||
if (value.find('=') == std::string::npos) {
|
||||
cache = NewLRUCache(ParseSizeT(value));
|
||||
} else {
|
||||
|
||||
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
+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
+48
@@ -765,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> {};
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#!/usr/bin/env python
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import optparse
|
||||
import re
|
||||
@@ -109,11 +108,11 @@ def report_coverage():
|
||||
|
||||
# Check if we need to display coverage info for interested files.
|
||||
if len(interested_files):
|
||||
per_file_coverage = dict(
|
||||
(fname, per_file_coverage[fname])
|
||||
per_file_coverage = {
|
||||
fname: per_file_coverage[fname]
|
||||
for fname in interested_files
|
||||
if fname in per_file_coverage
|
||||
)
|
||||
}
|
||||
# If we only interested in several files, it makes no sense to report
|
||||
# the total_coverage
|
||||
total_coverage = None
|
||||
|
||||
@@ -83,6 +83,8 @@ class ArenaWrappedDBIter : public Iterator {
|
||||
Status Refresh() override;
|
||||
Status Refresh(const Snapshot*) override;
|
||||
|
||||
bool PrepareValue() override { return db_iter_->PrepareValue(); }
|
||||
|
||||
void Init(Env* env, const ReadOptions& read_options,
|
||||
const ImmutableOptions& ioptions,
|
||||
const MutableCFOptions& mutable_cf_options, const Version* version,
|
||||
|
||||
@@ -13,14 +13,11 @@ namespace ROCKSDB_NAMESPACE {
|
||||
class AttributeGroupIteratorImpl : public AttributeGroupIterator {
|
||||
public:
|
||||
AttributeGroupIteratorImpl(
|
||||
const Comparator* comparator,
|
||||
const std::vector<ColumnFamilyHandle*>& column_families,
|
||||
const std::vector<Iterator*>& child_iterators)
|
||||
: impl_(
|
||||
comparator, column_families, child_iterators, [this]() { Reset(); },
|
||||
[this](const autovector<MultiCfIteratorInfo>& items) {
|
||||
AddToAttributeGroups(items);
|
||||
}) {}
|
||||
const ReadOptions& read_options, const Comparator* comparator,
|
||||
std::vector<std::pair<ColumnFamilyHandle*, std::unique_ptr<Iterator>>>&&
|
||||
cfh_iter_pairs)
|
||||
: impl_(read_options, comparator, std::move(cfh_iter_pairs),
|
||||
ResetFunc(this), PopulateFunc(this)) {}
|
||||
~AttributeGroupIteratorImpl() override {}
|
||||
|
||||
// No copy allowed
|
||||
@@ -45,8 +42,36 @@ class AttributeGroupIteratorImpl : public AttributeGroupIterator {
|
||||
|
||||
void Reset() { attribute_groups_.clear(); }
|
||||
|
||||
bool PrepareValue() override { return impl_.PrepareValue(); }
|
||||
|
||||
private:
|
||||
MultiCfIteratorImpl impl_;
|
||||
class ResetFunc {
|
||||
public:
|
||||
explicit ResetFunc(AttributeGroupIteratorImpl* iter) : iter_(iter) {}
|
||||
|
||||
void operator()() const {
|
||||
assert(iter_);
|
||||
iter_->Reset();
|
||||
}
|
||||
|
||||
private:
|
||||
AttributeGroupIteratorImpl* iter_;
|
||||
};
|
||||
|
||||
class PopulateFunc {
|
||||
public:
|
||||
explicit PopulateFunc(AttributeGroupIteratorImpl* iter) : iter_(iter) {}
|
||||
|
||||
void operator()(const autovector<MultiCfIteratorInfo>& items) const {
|
||||
assert(iter_);
|
||||
iter_->AddToAttributeGroups(items);
|
||||
}
|
||||
|
||||
private:
|
||||
AttributeGroupIteratorImpl* iter_;
|
||||
};
|
||||
|
||||
MultiCfIteratorImpl<ResetFunc, PopulateFunc> impl_;
|
||||
IteratorAttributeGroups attribute_groups_;
|
||||
void AddToAttributeGroups(const autovector<MultiCfIteratorInfo>& items);
|
||||
};
|
||||
|
||||
@@ -42,6 +42,7 @@ Status BlobFileCache::GetBlobFileReader(
|
||||
assert(blob_file_reader);
|
||||
assert(blob_file_reader->IsEmpty());
|
||||
|
||||
// NOTE: sharing same Cache with table_cache
|
||||
const Slice key = GetSliceForKey(&blob_file_number);
|
||||
|
||||
assert(cache_);
|
||||
@@ -98,4 +99,13 @@ Status BlobFileCache::GetBlobFileReader(
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
void BlobFileCache::Evict(uint64_t blob_file_number) {
|
||||
// NOTE: sharing same Cache with table_cache
|
||||
const Slice key = GetSliceForKey(&blob_file_number);
|
||||
|
||||
assert(cache_);
|
||||
|
||||
cache_.get()->Erase(key);
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -36,6 +36,15 @@ class BlobFileCache {
|
||||
uint64_t blob_file_number,
|
||||
CacheHandleGuard<BlobFileReader>* blob_file_reader);
|
||||
|
||||
// Called when a blob file is obsolete to ensure it is removed from the cache
|
||||
// to avoid effectively leaking the open file and assicated memory
|
||||
void Evict(uint64_t blob_file_number);
|
||||
|
||||
// Used to identify cache entries for blob files (not normally useful)
|
||||
static const Cache::CacheItemHelper* GetHelper() {
|
||||
return CacheInterface::GetBasicHelper();
|
||||
}
|
||||
|
||||
private:
|
||||
using CacheInterface =
|
||||
BasicTypedCacheInterface<BlobFileReader, CacheEntryRole::kMisc>;
|
||||
|
||||
@@ -20,23 +20,24 @@
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
BlobSource::BlobSource(const ImmutableOptions* immutable_options,
|
||||
BlobSource::BlobSource(const ImmutableOptions& immutable_options,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
const std::string& db_id,
|
||||
const std::string& db_session_id,
|
||||
BlobFileCache* blob_file_cache)
|
||||
: db_id_(db_id),
|
||||
db_session_id_(db_session_id),
|
||||
statistics_(immutable_options->statistics.get()),
|
||||
statistics_(immutable_options.statistics.get()),
|
||||
blob_file_cache_(blob_file_cache),
|
||||
blob_cache_(immutable_options->blob_cache),
|
||||
lowest_used_cache_tier_(immutable_options->lowest_used_cache_tier) {
|
||||
blob_cache_(immutable_options.blob_cache),
|
||||
lowest_used_cache_tier_(immutable_options.lowest_used_cache_tier) {
|
||||
auto bbto =
|
||||
immutable_options->table_factory->GetOptions<BlockBasedTableOptions>();
|
||||
mutable_cf_options.table_factory->GetOptions<BlockBasedTableOptions>();
|
||||
if (bbto &&
|
||||
bbto->cache_usage_options.options_overrides.at(CacheEntryRole::kBlobCache)
|
||||
.charged == CacheEntryRoleOptions::Decision::kEnabled) {
|
||||
blob_cache_ = SharedCacheInterface{std::make_shared<ChargedCache>(
|
||||
immutable_options->blob_cache, bbto->block_cache)};
|
||||
immutable_options.blob_cache, bbto->block_cache)};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
struct ImmutableOptions;
|
||||
struct MutableCFOptions;
|
||||
class Status;
|
||||
class FilePrefetchBuffer;
|
||||
class Slice;
|
||||
@@ -31,7 +32,10 @@ class Slice;
|
||||
// storage with minimal cost.
|
||||
class BlobSource {
|
||||
public:
|
||||
BlobSource(const ImmutableOptions* immutable_options,
|
||||
// NOTE: db_id, db_session_id, and blob_file_cache are saved by reference or
|
||||
// pointer.
|
||||
BlobSource(const ImmutableOptions& immutable_options,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
const std::string& db_id, const std::string& db_session_id,
|
||||
BlobFileCache* blob_file_cache);
|
||||
|
||||
|
||||
+22
-14
@@ -148,6 +148,7 @@ TEST_F(BlobSourceTest, GetBlobsFromCache) {
|
||||
DestroyAndReopen(options_);
|
||||
|
||||
ImmutableOptions immutable_options(options_);
|
||||
MutableCFOptions mutable_cf_options(options_);
|
||||
|
||||
constexpr uint32_t column_family_id = 1;
|
||||
constexpr bool has_ttl = false;
|
||||
@@ -193,8 +194,8 @@ TEST_F(BlobSourceTest, GetBlobsFromCache) {
|
||||
backing_cache.get(), &immutable_options, &file_options,
|
||||
column_family_id, blob_file_read_hist, nullptr /*IOTracer*/);
|
||||
|
||||
BlobSource blob_source(&immutable_options, db_id_, db_session_id_,
|
||||
blob_file_cache.get());
|
||||
BlobSource blob_source(immutable_options, mutable_cf_options, db_id_,
|
||||
db_session_id_, blob_file_cache.get());
|
||||
|
||||
ReadOptions read_options;
|
||||
read_options.verify_checksums = true;
|
||||
@@ -464,6 +465,7 @@ TEST_F(BlobSourceTest, GetCompressedBlobs) {
|
||||
DestroyAndReopen(options_);
|
||||
|
||||
ImmutableOptions immutable_options(options_);
|
||||
MutableCFOptions mutable_cf_options(options_);
|
||||
|
||||
constexpr uint32_t column_family_id = 1;
|
||||
constexpr bool has_ttl = false;
|
||||
@@ -498,8 +500,8 @@ TEST_F(BlobSourceTest, GetCompressedBlobs) {
|
||||
backing_cache.get(), &immutable_options, &file_options,
|
||||
column_family_id, nullptr /*HistogramImpl*/, nullptr /*IOTracer*/);
|
||||
|
||||
BlobSource blob_source(&immutable_options, db_id_, db_session_id_,
|
||||
blob_file_cache.get());
|
||||
BlobSource blob_source(immutable_options, mutable_cf_options, db_id_,
|
||||
db_session_id_, blob_file_cache.get());
|
||||
|
||||
ReadOptions read_options;
|
||||
read_options.verify_checksums = true;
|
||||
@@ -589,6 +591,7 @@ TEST_F(BlobSourceTest, MultiGetBlobsFromMultiFiles) {
|
||||
DestroyAndReopen(options_);
|
||||
|
||||
ImmutableOptions immutable_options(options_);
|
||||
MutableCFOptions mutable_cf_options(options_);
|
||||
|
||||
constexpr uint32_t column_family_id = 1;
|
||||
constexpr bool has_ttl = false;
|
||||
@@ -644,8 +647,8 @@ TEST_F(BlobSourceTest, MultiGetBlobsFromMultiFiles) {
|
||||
backing_cache.get(), &immutable_options, &file_options,
|
||||
column_family_id, blob_file_read_hist, nullptr /*IOTracer*/);
|
||||
|
||||
BlobSource blob_source(&immutable_options, db_id_, db_session_id_,
|
||||
blob_file_cache.get());
|
||||
BlobSource blob_source(immutable_options, mutable_cf_options, db_id_,
|
||||
db_session_id_, blob_file_cache.get());
|
||||
|
||||
ReadOptions read_options;
|
||||
read_options.verify_checksums = true;
|
||||
@@ -782,6 +785,7 @@ TEST_F(BlobSourceTest, MultiGetBlobsFromCache) {
|
||||
DestroyAndReopen(options_);
|
||||
|
||||
ImmutableOptions immutable_options(options_);
|
||||
MutableCFOptions mutable_cf_options(options_);
|
||||
|
||||
constexpr uint32_t column_family_id = 1;
|
||||
constexpr bool has_ttl = false;
|
||||
@@ -827,8 +831,8 @@ TEST_F(BlobSourceTest, MultiGetBlobsFromCache) {
|
||||
backing_cache.get(), &immutable_options, &file_options,
|
||||
column_family_id, blob_file_read_hist, nullptr /*IOTracer*/);
|
||||
|
||||
BlobSource blob_source(&immutable_options, db_id_, db_session_id_,
|
||||
blob_file_cache.get());
|
||||
BlobSource blob_source(immutable_options, mutable_cf_options, db_id_,
|
||||
db_session_id_, blob_file_cache.get());
|
||||
|
||||
ReadOptions read_options;
|
||||
read_options.verify_checksums = true;
|
||||
@@ -1105,6 +1109,7 @@ TEST_F(BlobSecondaryCacheTest, GetBlobsFromSecondaryCache) {
|
||||
DestroyAndReopen(options_);
|
||||
|
||||
ImmutableOptions immutable_options(options_);
|
||||
MutableCFOptions mutable_cf_options(options_);
|
||||
|
||||
constexpr uint32_t column_family_id = 1;
|
||||
constexpr bool has_ttl = false;
|
||||
@@ -1137,8 +1142,8 @@ TEST_F(BlobSecondaryCacheTest, GetBlobsFromSecondaryCache) {
|
||||
backing_cache.get(), &immutable_options, &file_options, column_family_id,
|
||||
blob_file_read_hist, nullptr /*IOTracer*/));
|
||||
|
||||
BlobSource blob_source(&immutable_options, db_id_, db_session_id_,
|
||||
blob_file_cache.get());
|
||||
BlobSource blob_source(immutable_options, mutable_cf_options, db_id_,
|
||||
db_session_id_, blob_file_cache.get());
|
||||
|
||||
CacheHandleGuard<BlobFileReader> file_reader;
|
||||
ReadOptions read_options;
|
||||
@@ -1405,6 +1410,7 @@ TEST_F(BlobSourceCacheReservationTest, SimpleCacheReservation) {
|
||||
DestroyAndReopen(options_);
|
||||
|
||||
ImmutableOptions immutable_options(options_);
|
||||
MutableCFOptions mutable_cf_options(options_);
|
||||
|
||||
constexpr ExpirationRange expiration_range;
|
||||
|
||||
@@ -1426,8 +1432,8 @@ TEST_F(BlobSourceCacheReservationTest, SimpleCacheReservation) {
|
||||
backing_cache.get(), &immutable_options, &file_options,
|
||||
kColumnFamilyId, blob_file_read_hist, nullptr /*IOTracer*/);
|
||||
|
||||
BlobSource blob_source(&immutable_options, db_id_, db_session_id_,
|
||||
blob_file_cache.get());
|
||||
BlobSource blob_source(immutable_options, mutable_cf_options, db_id_,
|
||||
db_session_id_, blob_file_cache.get());
|
||||
|
||||
ConcurrentCacheReservationManager* cache_res_mgr =
|
||||
static_cast<ChargedCache*>(blob_source.GetBlobCache())
|
||||
@@ -1519,6 +1525,8 @@ TEST_F(BlobSourceCacheReservationTest, IncreaseCacheReservation) {
|
||||
DestroyAndReopen(options_);
|
||||
|
||||
ImmutableOptions immutable_options(options_);
|
||||
MutableCFOptions mutable_cf_options(options_);
|
||||
|
||||
constexpr size_t blob_size = 24 << 10; // 24KB
|
||||
for (size_t i = 0; i < kNumBlobs; ++i) {
|
||||
blob_file_size_ -= blobs_[i].size(); // old blob size
|
||||
@@ -1546,8 +1554,8 @@ TEST_F(BlobSourceCacheReservationTest, IncreaseCacheReservation) {
|
||||
backing_cache.get(), &immutable_options, &file_options,
|
||||
kColumnFamilyId, blob_file_read_hist, nullptr /*IOTracer*/);
|
||||
|
||||
BlobSource blob_source(&immutable_options, db_id_, db_session_id_,
|
||||
blob_file_cache.get());
|
||||
BlobSource blob_source(immutable_options, mutable_cf_options, db_id_,
|
||||
db_session_id_, blob_file_cache.get());
|
||||
|
||||
ConcurrentCacheReservationManager* cache_res_mgr =
|
||||
static_cast<ChargedCache*>(blob_source.GetBlobCache())
|
||||
|
||||
@@ -374,6 +374,115 @@ TEST_F(DBBlobBasicTest, IterateBlobsFromCachePinning) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBBlobBasicTest, IterateBlobsAllowUnpreparedValue) {
|
||||
Options options = GetDefaultOptions();
|
||||
options.enable_blob_files = true;
|
||||
|
||||
Reopen(options);
|
||||
|
||||
constexpr size_t num_blobs = 5;
|
||||
std::vector<std::string> keys;
|
||||
std::vector<std::string> blobs;
|
||||
|
||||
for (size_t i = 0; i < num_blobs; ++i) {
|
||||
keys.emplace_back("key" + std::to_string(i));
|
||||
blobs.emplace_back("blob" + std::to_string(i));
|
||||
ASSERT_OK(Put(keys[i], blobs[i]));
|
||||
}
|
||||
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
ReadOptions read_options;
|
||||
read_options.allow_unprepared_value = true;
|
||||
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
|
||||
{
|
||||
size_t i = 0;
|
||||
|
||||
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
|
||||
ASSERT_EQ(iter->key(), keys[i]);
|
||||
ASSERT_TRUE(iter->value().empty());
|
||||
ASSERT_OK(iter->status());
|
||||
|
||||
ASSERT_TRUE(iter->PrepareValue());
|
||||
|
||||
ASSERT_EQ(iter->key(), keys[i]);
|
||||
ASSERT_EQ(iter->value(), blobs[i]);
|
||||
ASSERT_OK(iter->status());
|
||||
|
||||
++i;
|
||||
}
|
||||
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ(i, num_blobs);
|
||||
}
|
||||
|
||||
{
|
||||
size_t i = 0;
|
||||
|
||||
for (iter->SeekToLast(); iter->Valid(); iter->Prev()) {
|
||||
ASSERT_EQ(iter->key(), keys[num_blobs - 1 - i]);
|
||||
ASSERT_TRUE(iter->value().empty());
|
||||
ASSERT_OK(iter->status());
|
||||
|
||||
ASSERT_TRUE(iter->PrepareValue());
|
||||
|
||||
ASSERT_EQ(iter->key(), keys[num_blobs - 1 - i]);
|
||||
ASSERT_EQ(iter->value(), blobs[num_blobs - 1 - i]);
|
||||
ASSERT_OK(iter->status());
|
||||
|
||||
++i;
|
||||
}
|
||||
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ(i, num_blobs);
|
||||
}
|
||||
|
||||
{
|
||||
size_t i = 1;
|
||||
|
||||
for (iter->Seek(keys[i]); iter->Valid(); iter->Next()) {
|
||||
ASSERT_EQ(iter->key(), keys[i]);
|
||||
ASSERT_TRUE(iter->value().empty());
|
||||
ASSERT_OK(iter->status());
|
||||
|
||||
ASSERT_TRUE(iter->PrepareValue());
|
||||
|
||||
ASSERT_EQ(iter->key(), keys[i]);
|
||||
ASSERT_EQ(iter->value(), blobs[i]);
|
||||
ASSERT_OK(iter->status());
|
||||
|
||||
++i;
|
||||
}
|
||||
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ(i, num_blobs);
|
||||
}
|
||||
|
||||
{
|
||||
size_t i = 1;
|
||||
|
||||
for (iter->SeekForPrev(keys[num_blobs - 1 - i]); iter->Valid();
|
||||
iter->Prev()) {
|
||||
ASSERT_EQ(iter->key(), keys[num_blobs - 1 - i]);
|
||||
ASSERT_TRUE(iter->value().empty());
|
||||
ASSERT_OK(iter->status());
|
||||
|
||||
ASSERT_TRUE(iter->PrepareValue());
|
||||
|
||||
ASSERT_EQ(iter->key(), keys[num_blobs - 1 - i]);
|
||||
ASSERT_EQ(iter->value(), blobs[num_blobs - 1 - i]);
|
||||
ASSERT_OK(iter->status());
|
||||
|
||||
++i;
|
||||
}
|
||||
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ(i, num_blobs);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBBlobBasicTest, MultiGetBlobs) {
|
||||
constexpr size_t min_blob_size = 6;
|
||||
|
||||
@@ -1655,6 +1764,46 @@ TEST_P(DBBlobBasicIOErrorTest, CompactionFilterReadBlob_IOError) {
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
|
||||
TEST_P(DBBlobBasicIOErrorTest, IterateBlobsAllowUnpreparedValue_IOError) {
|
||||
Options options;
|
||||
options.env = fault_injection_env_.get();
|
||||
options.enable_blob_files = true;
|
||||
|
||||
Reopen(options);
|
||||
|
||||
constexpr char key[] = "key";
|
||||
constexpr char blob_value[] = "blob_value";
|
||||
|
||||
ASSERT_OK(Put(key, blob_value));
|
||||
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
SyncPoint::GetInstance()->SetCallBack(sync_point_, [this](void* /* arg */) {
|
||||
fault_injection_env_->SetFilesystemActive(false,
|
||||
Status::IOError(sync_point_));
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
ReadOptions read_options;
|
||||
read_options.allow_unprepared_value = true;
|
||||
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
iter->SeekToFirst();
|
||||
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ(iter->key(), key);
|
||||
ASSERT_TRUE(iter->value().empty());
|
||||
ASSERT_OK(iter->status());
|
||||
|
||||
ASSERT_FALSE(iter->PrepareValue());
|
||||
|
||||
ASSERT_FALSE(iter->Valid());
|
||||
ASSERT_TRUE(iter->status().IsIOError());
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
|
||||
TEST_F(DBBlobBasicTest, WarmCacheWithBlobsDuringFlush) {
|
||||
Options options = GetDefaultOptions();
|
||||
|
||||
|
||||
+4
-24
@@ -53,7 +53,7 @@ TableBuilder* NewTableBuilder(const TableBuilderOptions& tboptions,
|
||||
assert((tboptions.column_family_id ==
|
||||
TablePropertiesCollectorFactory::Context::kUnknownColumnFamily) ==
|
||||
tboptions.column_family_name.empty());
|
||||
return tboptions.ioptions.table_factory->NewTableBuilder(tboptions, file);
|
||||
return tboptions.moptions.table_factory->NewTableBuilder(tboptions, file);
|
||||
}
|
||||
|
||||
Status BuildTable(
|
||||
@@ -206,10 +206,6 @@ Status BuildTable(
|
||||
/*compaction=*/nullptr, compaction_filter.get(),
|
||||
/*shutting_down=*/nullptr, db_options.info_log, full_history_ts_low);
|
||||
|
||||
const size_t ts_sz = ucmp->timestamp_size();
|
||||
const bool logical_strip_timestamp =
|
||||
ts_sz > 0 && !ioptions.persist_user_defined_timestamps;
|
||||
|
||||
SequenceNumber smallest_preferred_seqno = kMaxSequenceNumber;
|
||||
std::string key_after_flush_buf;
|
||||
std::string value_buf;
|
||||
@@ -222,16 +218,6 @@ Status BuildTable(
|
||||
Slice key_after_flush = key_after_flush_buf;
|
||||
Slice value_after_flush = value;
|
||||
|
||||
// If user defined timestamps will be stripped from user key after flush,
|
||||
// the in memory version of the key act logically the same as one with a
|
||||
// minimum timestamp. We update the timestamp here so file boundary and
|
||||
// output validator, block builder all see the effect of the stripping.
|
||||
if (logical_strip_timestamp) {
|
||||
key_after_flush_buf.clear();
|
||||
ReplaceInternalKeyWithMinTimestamp(&key_after_flush_buf, key, ts_sz);
|
||||
key_after_flush = key_after_flush_buf;
|
||||
}
|
||||
|
||||
if (ikey.type == kTypeValuePreferredSeqno) {
|
||||
auto [unpacked_value, unix_write_time] =
|
||||
ParsePackedValueWithWriteTime(value);
|
||||
@@ -291,11 +277,7 @@ Status BuildTable(
|
||||
Slice last_tombstone_start_user_key{};
|
||||
for (range_del_it->SeekToFirst(); range_del_it->Valid();
|
||||
range_del_it->Next()) {
|
||||
// When user timestamp should not be persisted, we logically strip a
|
||||
// range tombstone's start and end key's timestamp (replace it with min
|
||||
// timestamp) before passing them along to table builder and to update
|
||||
// file boundaries.
|
||||
auto tombstone = range_del_it->Tombstone(logical_strip_timestamp);
|
||||
auto tombstone = range_del_it->Tombstone();
|
||||
std::pair<InternalKey, Slice> kv = tombstone.Serialize();
|
||||
builder->Add(kv.first.Encode(), kv.second);
|
||||
InternalKey tombstone_end = tombstone.SerializeEndKey();
|
||||
@@ -438,8 +420,7 @@ Status BuildTable(
|
||||
// the goal is to cache it here for further user reads.
|
||||
std::unique_ptr<InternalIterator> it(table_cache->NewIterator(
|
||||
tboptions.read_options, file_options, tboptions.internal_comparator,
|
||||
*meta, nullptr /* range_del_agg */,
|
||||
mutable_cf_options.prefix_extractor, nullptr,
|
||||
*meta, nullptr /* range_del_agg */, mutable_cf_options, nullptr,
|
||||
(internal_stats == nullptr) ? nullptr
|
||||
: internal_stats->GetFileReadHist(0),
|
||||
TableReaderCaller::kFlush, /*arena=*/nullptr,
|
||||
@@ -447,8 +428,7 @@ Status BuildTable(
|
||||
MaxFileSizeForL0MetaPin(mutable_cf_options),
|
||||
/*smallest_compaction_key=*/nullptr,
|
||||
/*largest_compaction_key*/ nullptr,
|
||||
/*allow_unprepared_value*/ false,
|
||||
mutable_cf_options.block_protection_bytes_per_key));
|
||||
/*allow_unprepared_value*/ false));
|
||||
s = it->status();
|
||||
if (s.ok() && paranoid_file_checks) {
|
||||
OutputValidator file_validator(tboptions.internal_comparator,
|
||||
|
||||
@@ -12,14 +12,12 @@ namespace ROCKSDB_NAMESPACE {
|
||||
// EXPERIMENTAL
|
||||
class CoalescingIterator : public Iterator {
|
||||
public:
|
||||
CoalescingIterator(const Comparator* comparator,
|
||||
const std::vector<ColumnFamilyHandle*>& column_families,
|
||||
const std::vector<Iterator*>& child_iterators)
|
||||
: impl_(
|
||||
comparator, column_families, child_iterators, [this]() { Reset(); },
|
||||
[this](const autovector<MultiCfIteratorInfo>& items) {
|
||||
Coalesce(items);
|
||||
}) {}
|
||||
CoalescingIterator(
|
||||
const ReadOptions& read_options, const Comparator* comparator,
|
||||
std::vector<std::pair<ColumnFamilyHandle*, std::unique_ptr<Iterator>>>&&
|
||||
cfh_iter_pairs)
|
||||
: impl_(read_options, comparator, std::move(cfh_iter_pairs),
|
||||
ResetFunc(this), PopulateFunc(this)) {}
|
||||
~CoalescingIterator() override {}
|
||||
|
||||
// No copy allowed
|
||||
@@ -50,8 +48,36 @@ class CoalescingIterator : public Iterator {
|
||||
wide_columns_.clear();
|
||||
}
|
||||
|
||||
bool PrepareValue() override { return impl_.PrepareValue(); }
|
||||
|
||||
private:
|
||||
MultiCfIteratorImpl impl_;
|
||||
class ResetFunc {
|
||||
public:
|
||||
explicit ResetFunc(CoalescingIterator* iter) : iter_(iter) {}
|
||||
|
||||
void operator()() const {
|
||||
assert(iter_);
|
||||
iter_->Reset();
|
||||
}
|
||||
|
||||
private:
|
||||
CoalescingIterator* iter_;
|
||||
};
|
||||
|
||||
class PopulateFunc {
|
||||
public:
|
||||
explicit PopulateFunc(CoalescingIterator* iter) : iter_(iter) {}
|
||||
|
||||
void operator()(const autovector<MultiCfIteratorInfo>& items) const {
|
||||
assert(iter_);
|
||||
iter_->Coalesce(items);
|
||||
}
|
||||
|
||||
private:
|
||||
CoalescingIterator* iter_;
|
||||
};
|
||||
|
||||
MultiCfIteratorImpl<ResetFunc, PopulateFunc> impl_;
|
||||
Slice value_;
|
||||
WideColumns wide_columns_;
|
||||
|
||||
|
||||
+12
-6
@@ -595,8 +595,8 @@ ColumnFamilyData::ColumnFamilyData(
|
||||
blob_file_cache_.reset(
|
||||
new BlobFileCache(_table_cache, ioptions(), soptions(), id_,
|
||||
internal_stats_->GetBlobFileReadHist(), io_tracer));
|
||||
blob_source_.reset(new BlobSource(ioptions(), db_id, db_session_id,
|
||||
blob_file_cache_.get()));
|
||||
blob_source_.reset(new BlobSource(ioptions_, mutable_cf_options_, db_id,
|
||||
db_session_id, blob_file_cache_.get()));
|
||||
|
||||
if (ioptions_.compaction_style == kCompactionStyleLevel) {
|
||||
compaction_picker_.reset(
|
||||
@@ -901,7 +901,11 @@ uint64_t GetPendingCompactionBytesForCompactionSpeedup(
|
||||
return slowdown_threshold;
|
||||
}
|
||||
|
||||
uint64_t size_threshold = bottommost_files_size / kBottommostSizeDivisor;
|
||||
// Prevent a small CF from triggering parallel compactions for other CFs.
|
||||
// Require compaction debt to be more than a full L0 to Lbase compaction.
|
||||
const uint64_t kMinDebtSize = 2 * mutable_cf_options.max_bytes_for_level_base;
|
||||
uint64_t size_threshold =
|
||||
std::max(bottommost_files_size / kBottommostSizeDivisor, kMinDebtSize);
|
||||
return std::min(size_threshold, slowdown_threshold);
|
||||
}
|
||||
|
||||
@@ -1172,10 +1176,12 @@ bool ColumnFamilyData::NeedsCompaction() const {
|
||||
|
||||
Compaction* ColumnFamilyData::PickCompaction(
|
||||
const MutableCFOptions& mutable_options,
|
||||
const MutableDBOptions& mutable_db_options, LogBuffer* log_buffer) {
|
||||
const MutableDBOptions& mutable_db_options,
|
||||
const std::vector<SequenceNumber>& existing_snapshots,
|
||||
const SnapshotChecker* snapshot_checker, LogBuffer* log_buffer) {
|
||||
auto* result = compaction_picker_->PickCompaction(
|
||||
GetName(), mutable_options, mutable_db_options, current_->storage_info(),
|
||||
log_buffer);
|
||||
GetName(), mutable_options, mutable_db_options, existing_snapshots,
|
||||
snapshot_checker, current_->storage_info(), log_buffer);
|
||||
if (result != nullptr) {
|
||||
result->FinalizeInputInfo(current_);
|
||||
}
|
||||
|
||||
+10
-6
@@ -16,6 +16,7 @@
|
||||
|
||||
#include "cache/cache_reservation_manager.h"
|
||||
#include "db/memtable_list.h"
|
||||
#include "db/snapshot_checker.h"
|
||||
#include "db/table_cache.h"
|
||||
#include "db/table_properties_collector.h"
|
||||
#include "db/write_batch_internal.h"
|
||||
@@ -385,9 +386,9 @@ class ColumnFamilyData {
|
||||
uint64_t GetTotalSstFilesSize() const; // REQUIRE: DB mutex held
|
||||
uint64_t GetLiveSstFilesSize() const; // REQUIRE: DB mutex held
|
||||
uint64_t GetTotalBlobFileSize() const; // REQUIRE: DB mutex held
|
||||
// REQUIRE: DB mutex held
|
||||
void SetMemtable(MemTable* new_mem) {
|
||||
uint64_t memtable_id = last_memtable_id_.fetch_add(1) + 1;
|
||||
new_mem->SetID(memtable_id);
|
||||
new_mem->SetID(++last_memtable_id_);
|
||||
mem_ = new_mem;
|
||||
}
|
||||
|
||||
@@ -401,15 +402,18 @@ 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
|
||||
// REQUIRES: DB mutex held
|
||||
bool NeedsCompaction() const;
|
||||
// REQUIRES: DB mutex held
|
||||
Compaction* PickCompaction(const MutableCFOptions& mutable_options,
|
||||
const MutableDBOptions& mutable_db_options,
|
||||
LogBuffer* log_buffer);
|
||||
Compaction* PickCompaction(
|
||||
const MutableCFOptions& mutable_options,
|
||||
const MutableDBOptions& mutable_db_options,
|
||||
const std::vector<SequenceNumber>& existing_snapshots,
|
||||
const SnapshotChecker* snapshot_checker, LogBuffer* log_buffer);
|
||||
|
||||
// Check if the passed range overlap with any running compactions.
|
||||
// REQUIRES: DB mutex held
|
||||
@@ -669,7 +673,7 @@ class ColumnFamilyData {
|
||||
bool allow_2pc_;
|
||||
|
||||
// Memtable id to track flush.
|
||||
std::atomic<uint64_t> last_memtable_id_;
|
||||
uint64_t last_memtable_id_;
|
||||
|
||||
// Directories corresponding to cf_paths.
|
||||
std::vector<std::shared_ptr<FSDirectory>> data_dirs_;
|
||||
|
||||
@@ -3012,19 +3012,25 @@ TEST_P(ColumnFamilyTest, CompactionSpeedupForCompactionDebt) {
|
||||
ASSERT_OK(db_->Flush(FlushOptions()));
|
||||
|
||||
{
|
||||
// 1MB debt is way bigger than bottommost data so definitely triggers
|
||||
// speedup.
|
||||
VersionStorageInfo* vstorage = cfd->current()->storage_info();
|
||||
vstorage->TEST_set_estimated_compaction_needed_bytes(1048576 /* 1MB */,
|
||||
dbmu);
|
||||
RecalculateWriteStallConditions(cfd, mutable_cf_options);
|
||||
ASSERT_EQ(6, dbfull()->TEST_BGCompactionsAllowed());
|
||||
|
||||
// Eight bytes is way smaller than bottommost data so definitely does not
|
||||
// trigger speedup.
|
||||
vstorage->TEST_set_estimated_compaction_needed_bytes(8, dbmu);
|
||||
RecalculateWriteStallConditions(cfd, mutable_cf_options);
|
||||
ASSERT_EQ(1, dbfull()->TEST_BGCompactionsAllowed());
|
||||
|
||||
// 1MB is much larger than bottommost level size. However, since it's too
|
||||
// small in terms of absolute size, it does not trigger parallel compaction
|
||||
// in this case (see GetPendingCompactionBytesForCompactionSpeedup()).
|
||||
vstorage->TEST_set_estimated_compaction_needed_bytes(1048576 /* 1MB */,
|
||||
dbmu);
|
||||
RecalculateWriteStallConditions(cfd, mutable_cf_options);
|
||||
ASSERT_EQ(1, dbfull()->TEST_BGCompactionsAllowed());
|
||||
|
||||
vstorage->TEST_set_estimated_compaction_needed_bytes(
|
||||
2 * mutable_cf_options.max_bytes_for_level_base, dbmu);
|
||||
RecalculateWriteStallConditions(cfd, mutable_cf_options);
|
||||
ASSERT_EQ(6, dbfull()->TEST_BGCompactionsAllowed());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3870,6 +3876,91 @@ TEST_F(ManualFlushSkipRetainUDTTest, ManualFlush) {
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(ManualFlushSkipRetainUDTTest, FlushRemovesStaleEntries) {
|
||||
column_family_options_.max_write_buffer_number = 4;
|
||||
Open();
|
||||
ASSERT_OK(db_->IncreaseFullHistoryTsLow(handles_[0], EncodeAsUint64(0)));
|
||||
|
||||
ColumnFamilyHandle* cfh = db_->DefaultColumnFamily();
|
||||
ColumnFamilyData* cfd =
|
||||
static_cast_with_check<ColumnFamilyHandleImpl>(cfh)->cfd();
|
||||
for (int version = 0; version < 100; version++) {
|
||||
if (version == 50) {
|
||||
ASSERT_OK(static_cast_with_check<DBImpl>(db_)->TEST_SwitchMemtable(cfd));
|
||||
}
|
||||
ASSERT_OK(
|
||||
Put(0, "foo", EncodeAsUint64(version), "v" + std::to_string(version)));
|
||||
}
|
||||
|
||||
ASSERT_OK(Flush(0));
|
||||
TablePropertiesCollection tables_properties;
|
||||
ASSERT_OK(db_->GetPropertiesOfAllTables(&tables_properties));
|
||||
ASSERT_EQ(1, tables_properties.size());
|
||||
std::shared_ptr<const TableProperties> table_properties =
|
||||
tables_properties.begin()->second;
|
||||
ASSERT_EQ(1, table_properties->num_entries);
|
||||
ASSERT_EQ(0, table_properties->num_deletions);
|
||||
ASSERT_EQ(0, table_properties->num_range_deletions);
|
||||
CheckEffectiveCutoffTime(100);
|
||||
CheckAutomaticFlushRetainUDT(101);
|
||||
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(ManualFlushSkipRetainUDTTest, RangeDeletionFlushRemovesStaleEntries) {
|
||||
column_family_options_.max_write_buffer_number = 4;
|
||||
Open();
|
||||
// TODO(yuzhangyu): a non 0 full history ts low is needed for this garbage
|
||||
// collection to kick in. This doesn't work well for the very first flush of
|
||||
// the column family. Not a big issue, but would be nice to improve this.
|
||||
ASSERT_OK(db_->IncreaseFullHistoryTsLow(handles_[0], EncodeAsUint64(9)));
|
||||
|
||||
for (int i = 10; i < 100; i++) {
|
||||
ASSERT_OK(Put(0, "foo" + std::to_string(i), EncodeAsUint64(i),
|
||||
"val" + std::to_string(i)));
|
||||
if (i % 2 == 1) {
|
||||
ASSERT_OK(db_->DeleteRange(WriteOptions(), "foo" + std::to_string(i - 1),
|
||||
"foo" + std::to_string(i), EncodeAsUint64(i)));
|
||||
}
|
||||
}
|
||||
|
||||
ASSERT_OK(Flush(0));
|
||||
CheckEffectiveCutoffTime(100);
|
||||
std::string read_ts = EncodeAsUint64(100);
|
||||
std::string min_ts = EncodeAsUint64(0);
|
||||
ReadOptions ropts;
|
||||
Slice read_ts_slice = read_ts;
|
||||
std::string value;
|
||||
ropts.timestamp = &read_ts_slice;
|
||||
{
|
||||
Iterator* iter = db_->NewIterator(ropts);
|
||||
iter->SeekToFirst();
|
||||
int i = 11;
|
||||
while (iter->Valid()) {
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ("foo" + std::to_string(i), iter->key());
|
||||
ASSERT_EQ("val" + std::to_string(i), iter->value());
|
||||
ASSERT_EQ(min_ts, iter->timestamp());
|
||||
iter->Next();
|
||||
i += 2;
|
||||
}
|
||||
ASSERT_OK(iter->status());
|
||||
delete iter;
|
||||
}
|
||||
TablePropertiesCollection tables_properties;
|
||||
ASSERT_OK(db_->GetPropertiesOfAllTables(&tables_properties));
|
||||
ASSERT_EQ(1, tables_properties.size());
|
||||
std::shared_ptr<const TableProperties> table_properties =
|
||||
tables_properties.begin()->second;
|
||||
// 45 point data + 45 range deletions. 45 obsolete point data are garbage
|
||||
// collected.
|
||||
ASSERT_EQ(90, table_properties->num_entries);
|
||||
ASSERT_EQ(45, table_properties->num_deletions);
|
||||
ASSERT_EQ(45, table_properties->num_range_deletions);
|
||||
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(ManualFlushSkipRetainUDTTest, ManualCompaction) {
|
||||
Open();
|
||||
ASSERT_OK(db_->IncreaseFullHistoryTsLow(handles_[0], EncodeAsUint64(0)));
|
||||
|
||||
+99
-12
@@ -283,9 +283,10 @@ Compaction::Compaction(
|
||||
uint32_t _output_path_id, CompressionType _compression,
|
||||
CompressionOptions _compression_opts, Temperature _output_temperature,
|
||||
uint32_t _max_subcompactions, std::vector<FileMetaData*> _grandparents,
|
||||
bool _manual_compaction, const std::string& _trim_ts, double _score,
|
||||
bool _deletion_compaction, bool l0_files_might_overlap,
|
||||
CompactionReason _compaction_reason,
|
||||
std::optional<SequenceNumber> _earliest_snapshot,
|
||||
const SnapshotChecker* _snapshot_checker, bool _manual_compaction,
|
||||
const std::string& _trim_ts, double _score, bool _deletion_compaction,
|
||||
bool l0_files_might_overlap, CompactionReason _compaction_reason,
|
||||
BlobGarbageCollectionPolicy _blob_garbage_collection_policy,
|
||||
double _blob_garbage_collection_age_cutoff)
|
||||
: input_vstorage_(vstorage),
|
||||
@@ -307,6 +308,8 @@ Compaction::Compaction(
|
||||
l0_files_might_overlap_(l0_files_might_overlap),
|
||||
inputs_(PopulateWithAtomicBoundaries(vstorage, std::move(_inputs))),
|
||||
grandparents_(std::move(_grandparents)),
|
||||
earliest_snapshot_(_earliest_snapshot),
|
||||
snapshot_checker_(_snapshot_checker),
|
||||
score_(_score),
|
||||
bottommost_level_(
|
||||
// For simplicity, we don't support the concept of "bottommost level"
|
||||
@@ -367,9 +370,13 @@ Compaction::Compaction(
|
||||
// setup input_levels_
|
||||
{
|
||||
input_levels_.resize(num_input_levels());
|
||||
for (size_t which = 0; which < num_input_levels(); which++) {
|
||||
DoGenerateLevelFilesBrief(&input_levels_[which], inputs_[which].files,
|
||||
&arena_);
|
||||
if (earliest_snapshot_.has_value()) {
|
||||
FilterInputsForCompactionIterator();
|
||||
} else {
|
||||
for (size_t which = 0; which < num_input_levels(); which++) {
|
||||
DoGenerateLevelFilesBrief(&input_levels_[which], inputs_[which].files,
|
||||
&arena_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -745,8 +752,10 @@ void Compaction::ResetNextCompactionIndex() {
|
||||
}
|
||||
|
||||
namespace {
|
||||
int InputSummary(const std::vector<FileMetaData*>& files, char* output,
|
||||
int InputSummary(const std::vector<FileMetaData*>& files,
|
||||
const std::vector<bool>& files_filtered, char* output,
|
||||
int len) {
|
||||
assert(files_filtered.empty() || (files.size() == files_filtered.size()));
|
||||
*output = '\0';
|
||||
int write = 0;
|
||||
for (size_t i = 0; i < files.size(); i++) {
|
||||
@@ -754,8 +763,14 @@ int InputSummary(const std::vector<FileMetaData*>& files, char* output,
|
||||
int ret;
|
||||
char sztxt[16];
|
||||
AppendHumanBytes(files.at(i)->fd.GetFileSize(), sztxt, 16);
|
||||
ret = snprintf(output + write, sz, "%" PRIu64 "(%s) ",
|
||||
files.at(i)->fd.GetNumber(), sztxt);
|
||||
if (files_filtered.empty()) {
|
||||
ret = snprintf(output + write, sz, "%" PRIu64 "(%s) ",
|
||||
files.at(i)->fd.GetNumber(), sztxt);
|
||||
} else {
|
||||
ret = snprintf(output + write, sz, "%" PRIu64 "(%s filtered:%s) ",
|
||||
files.at(i)->fd.GetNumber(), sztxt,
|
||||
files_filtered.at(i) ? "true" : "false");
|
||||
}
|
||||
if (ret < 0 || ret >= sz) {
|
||||
break;
|
||||
}
|
||||
@@ -781,8 +796,15 @@ void Compaction::Summary(char* output, int len) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
write +=
|
||||
InputSummary(inputs_[level_iter].files, output + write, len - write);
|
||||
|
||||
assert(non_start_level_input_files_filtered_.empty() ||
|
||||
non_start_level_input_files_filtered_.size() == inputs_.size() - 1);
|
||||
write += InputSummary(
|
||||
inputs_[level_iter].files,
|
||||
(level_iter == 0 || non_start_level_input_files_filtered_.empty())
|
||||
? std::vector<bool>{}
|
||||
: non_start_level_input_files_filtered_[level_iter - 1],
|
||||
output + write, len - write);
|
||||
if (write < 0 || write >= len) {
|
||||
return;
|
||||
}
|
||||
@@ -865,7 +887,7 @@ bool Compaction::ShouldFormSubcompactions() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (cfd_->ioptions()->table_factory->Name() ==
|
||||
if (mutable_cf_options_.table_factory->Name() ==
|
||||
TableFactory::kPlainTableName()) {
|
||||
return false;
|
||||
}
|
||||
@@ -991,4 +1013,69 @@ int Compaction::EvaluatePenultimateLevel(
|
||||
return penultimate_level;
|
||||
}
|
||||
|
||||
void Compaction::FilterInputsForCompactionIterator() {
|
||||
assert(earliest_snapshot_.has_value());
|
||||
// cfd_ is not populated at Compaction construction time, get it from
|
||||
// VersionStorageInfo instead.
|
||||
assert(input_vstorage_);
|
||||
const auto* ucmp = input_vstorage_->user_comparator();
|
||||
assert(ucmp);
|
||||
// Simply comparing file boundaries when user-defined timestamp is defined
|
||||
// is not as safe because we need to also compare timestamp to know for
|
||||
// sure. Although entries with higher timestamp is also supposed to have
|
||||
// higher sequence number for the same user key (without timestamp).
|
||||
assert(ucmp->timestamp_size() == 0);
|
||||
size_t num_input_levels = inputs_.size();
|
||||
// TODO(yuzhangyu): filtering of older L0 file by new L0 file is not
|
||||
// supported yet.
|
||||
FileMetaData* rangedel_candidate = inputs_[0].level == 0
|
||||
? inputs_[0].files.back()
|
||||
: inputs_[0].files.front();
|
||||
assert(rangedel_candidate);
|
||||
if (!rangedel_candidate->FileIsStandAloneRangeTombstone() ||
|
||||
!DataIsDefinitelyInSnapshot(rangedel_candidate->fd.smallest_seqno,
|
||||
earliest_snapshot_.value(),
|
||||
snapshot_checker_)) {
|
||||
for (size_t level = 0; level < num_input_levels; level++) {
|
||||
DoGenerateLevelFilesBrief(&input_levels_[level], inputs_[level].files,
|
||||
&arena_);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
Slice rangedel_start_ukey = rangedel_candidate->smallest.user_key();
|
||||
Slice rangedel_end_ukey = rangedel_candidate->largest.user_key();
|
||||
SequenceNumber rangedel_seqno = rangedel_candidate->fd.smallest_seqno;
|
||||
|
||||
std::vector<std::vector<FileMetaData*>> non_start_level_input_files;
|
||||
non_start_level_input_files.reserve(num_input_levels - 1);
|
||||
non_start_level_input_files_filtered_.reserve(num_input_levels - 1);
|
||||
for (size_t level = 1; level < num_input_levels; level++) {
|
||||
non_start_level_input_files.emplace_back();
|
||||
non_start_level_input_files_filtered_.emplace_back();
|
||||
for (FileMetaData* file : inputs_[level].files) {
|
||||
non_start_level_input_files_filtered_.back().push_back(false);
|
||||
// When range data and point data has the same sequence number, point
|
||||
// data wins. Range deletion end key is exclusive, so check it's bigger
|
||||
// than file right boundary user key.
|
||||
if (rangedel_seqno > file->fd.largest_seqno &&
|
||||
ucmp->CompareWithoutTimestamp(rangedel_start_ukey,
|
||||
file->smallest.user_key()) <= 0 &&
|
||||
ucmp->CompareWithoutTimestamp(rangedel_end_ukey,
|
||||
file->largest.user_key()) > 0) {
|
||||
non_start_level_input_files_filtered_.back().back() = true;
|
||||
} else {
|
||||
non_start_level_input_files.back().push_back(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DoGenerateLevelFilesBrief(&input_levels_[0], inputs_[0].files, &arena_);
|
||||
assert(non_start_level_input_files.size() == num_input_levels - 1);
|
||||
for (size_t level = 1; level < num_input_levels; level++) {
|
||||
DoGenerateLevelFilesBrief(&input_levels_[level],
|
||||
non_start_level_input_files[level - 1], &arena_);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -8,6 +8,8 @@
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "db/snapshot_checker.h"
|
||||
#include "db/version_set.h"
|
||||
#include "memory/arena.h"
|
||||
#include "options/cf_options.h"
|
||||
@@ -90,6 +92,8 @@ class Compaction {
|
||||
CompressionOptions compression_opts,
|
||||
Temperature output_temperature, uint32_t max_subcompactions,
|
||||
std::vector<FileMetaData*> grandparents,
|
||||
std::optional<SequenceNumber> earliest_snapshot,
|
||||
const SnapshotChecker* snapshot_checker,
|
||||
bool manual_compaction = false, const std::string& trim_ts = "",
|
||||
double score = -1, bool deletion_compaction = false,
|
||||
bool l0_files_might_overlap = true,
|
||||
@@ -460,6 +464,13 @@ class Compaction {
|
||||
// `Compaction::WithinPenultimateLevelOutputRange()`.
|
||||
void PopulatePenultimateLevelOutputRange();
|
||||
|
||||
// If oldest snapshot is specified at Compaction construction time, we have
|
||||
// an opportunity to optimize inputs for compaction iterator for this case:
|
||||
// When a standalone range deletion file on the start level is recognized and
|
||||
// can be determined to completely shadow some input files on non-start level.
|
||||
// These files will be filtered out and later not feed to compaction iterator.
|
||||
void FilterInputsForCompactionIterator();
|
||||
|
||||
// Get the atomic file boundaries for all files in the compaction. Necessary
|
||||
// in order to avoid the scenario described in
|
||||
// https://github.com/facebook/rocksdb/pull/4432#discussion_r221072219 and
|
||||
@@ -510,12 +521,27 @@ class Compaction {
|
||||
// Compaction input files organized by level. Constant after construction
|
||||
const std::vector<CompactionInputFiles> inputs_;
|
||||
|
||||
// A copy of inputs_, organized more closely in memory
|
||||
// All files from inputs_ that are not filtered and will be fed to compaction
|
||||
// iterator, organized more closely in memory.
|
||||
autovector<LevelFilesBrief, 2> input_levels_;
|
||||
|
||||
// State used to check for number of overlapping grandparent files
|
||||
// (grandparent == "output_level_ + 1")
|
||||
std::vector<FileMetaData*> grandparents_;
|
||||
|
||||
// The earliest snapshot and snapshot checker at compaction picking time.
|
||||
// These fields are only set for deletion triggered compactions picked in
|
||||
// universal compaction. And when user-defined timestamp is not enabled.
|
||||
// It will be used to possibly filter out some non start level input files.
|
||||
std::optional<SequenceNumber> earliest_snapshot_;
|
||||
const SnapshotChecker* snapshot_checker_;
|
||||
|
||||
// Markers for which non start level input files are filtered out if
|
||||
// applicable. Only applicable if earliest_snapshot_ is provided and input
|
||||
// start level has a standalone range deletion file.
|
||||
std::vector<std::vector<bool>> non_start_level_input_files_filtered_;
|
||||
|
||||
// bool standalone_range_tombstones_used_for_filtering_inputs_;
|
||||
const double score_; // score that was used to pick this compaction.
|
||||
|
||||
// Is this compaction creating a file in the bottom most level?
|
||||
|
||||
@@ -540,18 +540,12 @@ class CompactionIterator {
|
||||
|
||||
inline bool CompactionIterator::DefinitelyInSnapshot(SequenceNumber seq,
|
||||
SequenceNumber snapshot) {
|
||||
return ((seq) <= (snapshot) &&
|
||||
(snapshot_checker_ == nullptr ||
|
||||
LIKELY(snapshot_checker_->CheckInSnapshot((seq), (snapshot)) ==
|
||||
SnapshotCheckerResult::kInSnapshot)));
|
||||
return DataIsDefinitelyInSnapshot(seq, snapshot, snapshot_checker_);
|
||||
}
|
||||
|
||||
inline bool CompactionIterator::DefinitelyNotInSnapshot(
|
||||
SequenceNumber seq, SequenceNumber snapshot) {
|
||||
return ((seq) > (snapshot) ||
|
||||
(snapshot_checker_ != nullptr &&
|
||||
UNLIKELY(snapshot_checker_->CheckInSnapshot((seq), (snapshot)) ==
|
||||
SnapshotCheckerResult::kNotInSnapshot)));
|
||||
return DataIsDefinitelyNotInSnapshot(seq, snapshot, snapshot_checker_);
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -469,7 +469,7 @@ void CompactionJob::GenSubcompactionBoundaries() {
|
||||
ReadOptions read_options(Env::IOActivity::kCompaction);
|
||||
read_options.rate_limiter_priority = GetRateLimiterPriority();
|
||||
auto* c = compact_->compaction;
|
||||
if (c->immutable_options()->table_factory->Name() ==
|
||||
if (c->mutable_cf_options()->table_factory->Name() ==
|
||||
TableFactory::kPlainTableName()) {
|
||||
return;
|
||||
}
|
||||
@@ -506,9 +506,7 @@ void CompactionJob::GenSubcompactionBoundaries() {
|
||||
FileMetaData* f = flevel->files[i].file_metadata;
|
||||
std::vector<TableReader::Anchor> my_anchors;
|
||||
Status s = cfd->table_cache()->ApproximateKeyAnchors(
|
||||
read_options, icomp, *f,
|
||||
c->mutable_cf_options()->block_protection_bytes_per_key,
|
||||
my_anchors);
|
||||
read_options, icomp, *f, *c->mutable_cf_options(), my_anchors);
|
||||
if (!s.ok() || my_anchors.empty()) {
|
||||
my_anchors.emplace_back(f->largest.user_key(), f->fd.GetFileSize());
|
||||
}
|
||||
@@ -711,8 +709,6 @@ Status CompactionJob::Run() {
|
||||
}
|
||||
}
|
||||
ColumnFamilyData* cfd = compact_->compaction->column_family_data();
|
||||
auto& prefix_extractor =
|
||||
compact_->compaction->mutable_cf_options()->prefix_extractor;
|
||||
std::atomic<size_t> next_file_idx(0);
|
||||
auto verify_table = [&](Status& output_status) {
|
||||
while (true) {
|
||||
@@ -733,7 +729,8 @@ Status CompactionJob::Run() {
|
||||
InternalIterator* iter = cfd->table_cache()->NewIterator(
|
||||
verify_table_read_options, file_options_,
|
||||
cfd->internal_comparator(), files_output[file_idx]->meta,
|
||||
/*range_del_agg=*/nullptr, prefix_extractor,
|
||||
/*range_del_agg=*/nullptr,
|
||||
*compact_->compaction->mutable_cf_options(),
|
||||
/*table_reader_ptr=*/nullptr,
|
||||
cfd->internal_stats()->GetFileReadHist(
|
||||
compact_->compaction->output_level()),
|
||||
@@ -743,9 +740,7 @@ Status CompactionJob::Run() {
|
||||
*compact_->compaction->mutable_cf_options()),
|
||||
/*smallest_compaction_key=*/nullptr,
|
||||
/*largest_compaction_key=*/nullptr,
|
||||
/*allow_unprepared_value=*/false,
|
||||
compact_->compaction->mutable_cf_options()
|
||||
->block_protection_bytes_per_key);
|
||||
/*allow_unprepared_value=*/false);
|
||||
auto s = iter->status();
|
||||
|
||||
if (s.ok() && paranoid_file_checks_) {
|
||||
@@ -806,6 +801,12 @@ Status CompactionJob::Run() {
|
||||
}
|
||||
}
|
||||
|
||||
// Before the compaction starts, is_remote_compaction was set to true if
|
||||
// compaction_service is set. We now know whether each sub_compaction was
|
||||
// done remotely or not. Reset is_remote_compaction back to false and allow
|
||||
// AggregateCompactionStats() to set the right value.
|
||||
compaction_job_stats_->is_remote_compaction = false;
|
||||
|
||||
// Finish up all bookkeeping to unify the subcompaction results.
|
||||
compact_->AggregateCompactionStats(compaction_stats_, *compaction_job_stats_);
|
||||
uint64_t num_input_range_del = 0;
|
||||
@@ -1084,6 +1085,7 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
}
|
||||
// fallback to local compaction
|
||||
assert(comp_status == CompactionServiceJobStatus::kUseLocal);
|
||||
sub_compact->compaction_job_stats.is_remote_compaction = false;
|
||||
}
|
||||
|
||||
uint64_t prev_cpu_micros = db_options_.clock->CPUMicros();
|
||||
@@ -2001,10 +2003,12 @@ bool CompactionJob::UpdateCompactionStats(uint64_t* num_input_range_del) {
|
||||
bool has_error = false;
|
||||
const ReadOptions read_options(Env::IOActivity::kCompaction);
|
||||
const auto& input_table_properties = compaction->GetInputTableProperties();
|
||||
// TODO(yuzhangyu): add dedicated stats for filtered files.
|
||||
for (int input_level = 0;
|
||||
input_level < static_cast<int>(compaction->num_input_levels());
|
||||
++input_level) {
|
||||
size_t num_input_files = compaction->num_input_files(input_level);
|
||||
const LevelFilesBrief* flevel = compaction->input_levels(input_level);
|
||||
size_t num_input_files = flevel->num_files;
|
||||
uint64_t* bytes_read;
|
||||
if (compaction->level(input_level) != compaction->output_level()) {
|
||||
compaction_stats_.stats.num_input_files_in_non_output_levels +=
|
||||
@@ -2016,7 +2020,7 @@ bool CompactionJob::UpdateCompactionStats(uint64_t* num_input_range_del) {
|
||||
bytes_read = &compaction_stats_.stats.bytes_read_output_level;
|
||||
}
|
||||
for (size_t i = 0; i < num_input_files; ++i) {
|
||||
const FileMetaData* file_meta = compaction->input(input_level, i);
|
||||
const FileMetaData* file_meta = flevel->files[i].file_metadata;
|
||||
*bytes_read += file_meta->fd.GetFileSize();
|
||||
uint64_t file_input_entries = file_meta->num_entries;
|
||||
uint64_t file_num_range_del = file_meta->num_range_deletions;
|
||||
|
||||
@@ -209,6 +209,8 @@ class CompactionJob {
|
||||
// Returns true iff compaction_stats_.stats.num_input_records and
|
||||
// num_input_range_del are calculated successfully.
|
||||
bool UpdateCompactionStats(uint64_t* num_input_range_del = nullptr);
|
||||
virtual void UpdateCompactionJobStats(
|
||||
const InternalStats::CompactionStats& stats) const;
|
||||
void LogCompaction();
|
||||
virtual void RecordCompactionIOStats();
|
||||
void CleanupCompaction();
|
||||
@@ -279,8 +281,7 @@ class CompactionJob {
|
||||
bool* compaction_released);
|
||||
Status OpenCompactionOutputFile(SubcompactionState* sub_compact,
|
||||
CompactionOutputs& outputs);
|
||||
void UpdateCompactionJobStats(
|
||||
const InternalStats::CompactionStats& stats) const;
|
||||
|
||||
void RecordDroppedKeys(const CompactionIterationStats& c_iter_stats,
|
||||
CompactionJobStats* compaction_job_stats = nullptr);
|
||||
|
||||
@@ -385,7 +386,7 @@ struct CompactionServiceInput {
|
||||
// files needed for this compaction, for both input level files and output
|
||||
// level files.
|
||||
std::vector<std::string> input_files;
|
||||
int output_level;
|
||||
int output_level = 0;
|
||||
|
||||
// db_id is used to generate unique id of sst on the remote compactor
|
||||
std::string db_id;
|
||||
@@ -396,6 +397,8 @@ struct CompactionServiceInput {
|
||||
bool has_end = false;
|
||||
std::string end;
|
||||
|
||||
uint64_t options_file_number = 0;
|
||||
|
||||
// serialization interface to read and write the object
|
||||
static Status Read(const std::string& data_str, CompactionServiceInput* obj);
|
||||
Status Write(std::string* output);
|
||||
@@ -413,20 +416,25 @@ struct CompactionServiceOutputFile {
|
||||
SequenceNumber largest_seqno;
|
||||
std::string smallest_internal_key;
|
||||
std::string largest_internal_key;
|
||||
uint64_t oldest_ancester_time;
|
||||
uint64_t file_creation_time;
|
||||
uint64_t epoch_number;
|
||||
uint64_t oldest_ancester_time = kUnknownOldestAncesterTime;
|
||||
uint64_t file_creation_time = kUnknownFileCreationTime;
|
||||
uint64_t epoch_number = kUnknownEpochNumber;
|
||||
std::string file_checksum = kUnknownFileChecksum;
|
||||
std::string file_checksum_func_name = kUnknownFileChecksumFuncName;
|
||||
uint64_t paranoid_hash;
|
||||
bool marked_for_compaction;
|
||||
UniqueId64x2 unique_id;
|
||||
UniqueId64x2 unique_id{};
|
||||
TableProperties table_properties;
|
||||
|
||||
CompactionServiceOutputFile() = default;
|
||||
CompactionServiceOutputFile(
|
||||
const std::string& name, SequenceNumber smallest, SequenceNumber largest,
|
||||
std::string _smallest_internal_key, std::string _largest_internal_key,
|
||||
uint64_t _oldest_ancester_time, uint64_t _file_creation_time,
|
||||
uint64_t _epoch_number, uint64_t _paranoid_hash,
|
||||
bool _marked_for_compaction, UniqueId64x2 _unique_id)
|
||||
uint64_t _epoch_number, const std::string& _file_checksum,
|
||||
const std::string& _file_checksum_func_name, uint64_t _paranoid_hash,
|
||||
bool _marked_for_compaction, UniqueId64x2 _unique_id,
|
||||
const std::shared_ptr<const TableProperties>& _table_properties)
|
||||
: file_name(name),
|
||||
smallest_seqno(smallest),
|
||||
largest_seqno(largest),
|
||||
@@ -435,9 +443,12 @@ struct CompactionServiceOutputFile {
|
||||
oldest_ancester_time(_oldest_ancester_time),
|
||||
file_creation_time(_file_creation_time),
|
||||
epoch_number(_epoch_number),
|
||||
file_checksum(_file_checksum),
|
||||
file_checksum_func_name(_file_checksum_func_name),
|
||||
paranoid_hash(_paranoid_hash),
|
||||
marked_for_compaction(_marked_for_compaction),
|
||||
unique_id(std::move(_unique_id)) {}
|
||||
unique_id(std::move(_unique_id)),
|
||||
table_properties(*_table_properties.get()) {}
|
||||
};
|
||||
|
||||
// CompactionServiceResult contains the compaction result from a different db
|
||||
@@ -446,14 +457,11 @@ struct CompactionServiceOutputFile {
|
||||
struct CompactionServiceResult {
|
||||
Status status;
|
||||
std::vector<CompactionServiceOutputFile> output_files;
|
||||
int output_level;
|
||||
int output_level = 0;
|
||||
|
||||
// location of the output files
|
||||
std::string output_path;
|
||||
|
||||
// some statistics about the compaction
|
||||
uint64_t num_output_records = 0;
|
||||
uint64_t total_bytes = 0;
|
||||
uint64_t bytes_read = 0;
|
||||
uint64_t bytes_written = 0;
|
||||
CompactionJobStats stats;
|
||||
@@ -499,6 +507,9 @@ class CompactionServiceCompactionJob : private CompactionJob {
|
||||
protected:
|
||||
void RecordCompactionIOStats() override;
|
||||
|
||||
void UpdateCompactionJobStats(
|
||||
const InternalStats::CompactionStats& stats) const override;
|
||||
|
||||
private:
|
||||
// Get table file name in output_path
|
||||
std::string GetTableFileName(uint64_t file_number) override;
|
||||
|
||||
@@ -50,7 +50,8 @@ void VerifyInitializationOfCompactionJobStats(
|
||||
ASSERT_EQ(compaction_job_stats.num_output_records, 0U);
|
||||
ASSERT_EQ(compaction_job_stats.num_output_files, 0U);
|
||||
|
||||
ASSERT_EQ(compaction_job_stats.is_manual_compaction, true);
|
||||
ASSERT_TRUE(compaction_job_stats.is_manual_compaction);
|
||||
ASSERT_FALSE(compaction_job_stats.is_remote_compaction);
|
||||
|
||||
ASSERT_EQ(compaction_job_stats.total_input_bytes, 0U);
|
||||
ASSERT_EQ(compaction_job_stats.total_output_bytes, 0U);
|
||||
@@ -249,6 +250,7 @@ class CompactionJobTestBase : public testing::Test {
|
||||
} else {
|
||||
assert(false);
|
||||
}
|
||||
mutable_cf_options_.table_factory = cf_options_.table_factory;
|
||||
}
|
||||
|
||||
std::string GenerateFileName(uint64_t file_number) {
|
||||
@@ -650,7 +652,8 @@ class CompactionJobTestBase : public testing::Test {
|
||||
mutable_cf_options_.target_file_size_base,
|
||||
mutable_cf_options_.max_compaction_bytes, 0, kNoCompression,
|
||||
cfd->GetLatestMutableCFOptions()->compression_opts,
|
||||
Temperature::kUnknown, max_subcompactions, grandparents, true);
|
||||
Temperature::kUnknown, max_subcompactions, grandparents,
|
||||
/*earliest_snapshot*/ std::nullopt, /*snapshot_checker*/ nullptr, true);
|
||||
compaction.FinalizeInputInfo(cfd->current());
|
||||
|
||||
assert(db_options_.info_log);
|
||||
@@ -1655,20 +1658,44 @@ TEST_F(CompactionJobTest, ResultSerialization) {
|
||||
};
|
||||
result.status =
|
||||
status_list.at(rnd.Uniform(static_cast<int>(status_list.size())));
|
||||
|
||||
std::string file_checksum = rnd.RandomBinaryString(rnd.Uniform(kStrMaxLen));
|
||||
std::string file_checksum_func_name = "MyAwesomeChecksumGenerator";
|
||||
while (!rnd.OneIn(10)) {
|
||||
TableProperties tp;
|
||||
tp.user_collected_properties.emplace(
|
||||
"UCP_Key1", rnd.RandomString(rnd.Uniform(kStrMaxLen)));
|
||||
tp.user_collected_properties.emplace(
|
||||
"UCP_Key2", rnd.RandomString(rnd.Uniform(kStrMaxLen)));
|
||||
tp.readable_properties.emplace("RP_Key1",
|
||||
rnd.RandomString(rnd.Uniform(kStrMaxLen)));
|
||||
tp.readable_properties.emplace("RP_K2y2",
|
||||
rnd.RandomString(rnd.Uniform(kStrMaxLen)));
|
||||
|
||||
std::shared_ptr<const TableProperties> table_properties =
|
||||
std::make_shared<const TableProperties>(tp);
|
||||
|
||||
UniqueId64x2 id{rnd64.Uniform(UINT64_MAX), rnd64.Uniform(UINT64_MAX)};
|
||||
result.output_files.emplace_back(
|
||||
rnd.RandomString(rnd.Uniform(kStrMaxLen)), rnd64.Uniform(UINT64_MAX),
|
||||
rnd64.Uniform(UINT64_MAX),
|
||||
rnd.RandomBinaryString(rnd.Uniform(kStrMaxLen)),
|
||||
rnd.RandomBinaryString(rnd.Uniform(kStrMaxLen)),
|
||||
rnd64.Uniform(UINT64_MAX), rnd64.Uniform(UINT64_MAX),
|
||||
rnd64.Uniform(UINT64_MAX), rnd64.Uniform(UINT64_MAX), rnd.OneIn(2), id);
|
||||
rnd.RandomString(rnd.Uniform(kStrMaxLen)) /* file_name */,
|
||||
rnd64.Uniform(UINT64_MAX) /* smallest_seqno */,
|
||||
rnd64.Uniform(UINT64_MAX) /* largest_seqno */,
|
||||
rnd.RandomBinaryString(
|
||||
rnd.Uniform(kStrMaxLen)) /* smallest_internal_key */,
|
||||
rnd.RandomBinaryString(
|
||||
rnd.Uniform(kStrMaxLen)) /* largest_internal_key */,
|
||||
rnd64.Uniform(UINT64_MAX) /* oldest_ancester_time */,
|
||||
rnd64.Uniform(UINT64_MAX) /* file_creation_time */,
|
||||
rnd64.Uniform(UINT64_MAX) /* epoch_number */,
|
||||
file_checksum /* file_checksum */,
|
||||
file_checksum_func_name /* file_checksum_func_name */,
|
||||
rnd64.Uniform(UINT64_MAX) /* paranoid_hash */,
|
||||
rnd.OneIn(2) /* marked_for_compaction */, id /* unique_id */,
|
||||
table_properties);
|
||||
}
|
||||
result.output_level = rnd.Uniform(10);
|
||||
result.output_path = rnd.RandomString(rnd.Uniform(kStrMaxLen));
|
||||
result.num_output_records = rnd64.Uniform(UINT64_MAX);
|
||||
result.total_bytes = rnd64.Uniform(UINT64_MAX);
|
||||
result.stats.num_output_records = rnd64.Uniform(UINT64_MAX);
|
||||
result.bytes_read = 123;
|
||||
result.bytes_written = rnd64.Uniform(UINT64_MAX);
|
||||
result.stats.elapsed_micros = rnd64.Uniform(UINT64_MAX);
|
||||
@@ -1685,6 +1712,21 @@ TEST_F(CompactionJobTest, ResultSerialization) {
|
||||
ASSERT_OK(CompactionServiceResult::Read(output, &deserialized1));
|
||||
ASSERT_TRUE(deserialized1.TEST_Equals(&result));
|
||||
|
||||
for (size_t i = 0; i < result.output_files.size(); i++) {
|
||||
for (const auto& prop :
|
||||
result.output_files[i].table_properties.user_collected_properties) {
|
||||
ASSERT_EQ(deserialized1.output_files[i]
|
||||
.table_properties.user_collected_properties[prop.first],
|
||||
prop.second);
|
||||
}
|
||||
for (const auto& prop :
|
||||
result.output_files[i].table_properties.readable_properties) {
|
||||
ASSERT_EQ(deserialized1.output_files[i]
|
||||
.table_properties.readable_properties[prop.first],
|
||||
prop.second);
|
||||
}
|
||||
}
|
||||
|
||||
// Test mismatch
|
||||
deserialized1.stats.num_input_files += 10;
|
||||
std::string mismatch;
|
||||
@@ -1699,6 +1741,10 @@ TEST_F(CompactionJobTest, ResultSerialization) {
|
||||
ASSERT_FALSE(deserialized_tmp.TEST_Equals(&result, &mismatch));
|
||||
ASSERT_EQ(mismatch, "output_files.unique_id");
|
||||
deserialized_tmp.status.PermitUncheckedError();
|
||||
|
||||
ASSERT_EQ(deserialized_tmp.output_files[0].file_checksum, file_checksum);
|
||||
ASSERT_EQ(deserialized_tmp.output_files[0].file_checksum_func_name,
|
||||
file_checksum_func_name);
|
||||
}
|
||||
|
||||
// Test unknown field
|
||||
|
||||
@@ -62,8 +62,9 @@ class CompactionOutputs {
|
||||
}
|
||||
|
||||
// TODO: Remove it when remote compaction support tiered compaction
|
||||
void SetTotalBytes(uint64_t bytes) { stats_.bytes_written += bytes; }
|
||||
void AddBytesWritten(uint64_t bytes) { stats_.bytes_written += bytes; }
|
||||
void SetNumOutputRecords(uint64_t num) { stats_.num_output_records = num; }
|
||||
void SetNumOutputFiles(uint64_t num) { stats_.num_output_files = num; }
|
||||
|
||||
// TODO: Move the BlobDB builder into CompactionOutputs
|
||||
const std::vector<BlobFileAddition>& GetBlobFileAdditions() const {
|
||||
@@ -107,6 +108,12 @@ class CompactionOutputs {
|
||||
Status Finish(const Status& intput_status,
|
||||
const SeqnoToTimeMapping& seqno_to_time_mapping);
|
||||
|
||||
// Update output table properties from already populated TableProperties.
|
||||
// Used for remote compaction
|
||||
void UpdateTableProperties(const TableProperties& table_properties) {
|
||||
current_output().table_properties =
|
||||
std::make_shared<TableProperties>(table_properties);
|
||||
}
|
||||
// Update output table properties from table builder
|
||||
void UpdateTableProperties() {
|
||||
current_output().table_properties =
|
||||
|
||||
@@ -380,7 +380,8 @@ Compaction* CompactionPicker::CompactFiles(
|
||||
GetCompressionOptions(mutable_cf_options, vstorage, output_level),
|
||||
mutable_cf_options.default_write_temperature,
|
||||
compact_options.max_subcompactions,
|
||||
/* grandparents */ {}, true);
|
||||
/* grandparents */ {}, /* earliest_snapshot */ std::nullopt,
|
||||
/* snapshot_checker */ nullptr, true);
|
||||
RegisterCompaction(c);
|
||||
return c;
|
||||
}
|
||||
@@ -677,7 +678,9 @@ Compaction* CompactionPicker::CompactRange(
|
||||
GetCompressionOptions(mutable_cf_options, vstorage, output_level),
|
||||
mutable_cf_options.default_write_temperature,
|
||||
compact_range_options.max_subcompactions,
|
||||
/* grandparents */ {}, /* is manual */ true, trim_ts, /* score */ -1,
|
||||
/* grandparents */ {}, /* earliest_snapshot */ std::nullopt,
|
||||
/* snapshot_checker */ nullptr,
|
||||
/* is manual */ true, trim_ts, /* score */ -1,
|
||||
/* deletion_compaction */ false, /* l0_files_might_overlap */ true,
|
||||
CompactionReason::kUnknown,
|
||||
compact_range_options.blob_garbage_collection_policy,
|
||||
@@ -866,6 +869,7 @@ Compaction* CompactionPicker::CompactRange(
|
||||
GetCompressionOptions(mutable_cf_options, vstorage, output_level),
|
||||
mutable_cf_options.default_write_temperature,
|
||||
compact_range_options.max_subcompactions, std::move(grandparents),
|
||||
/* earliest_snapshot */ std::nullopt, /* snapshot_checker */ nullptr,
|
||||
/* is manual */ true, trim_ts, /* score */ -1,
|
||||
/* deletion_compaction */ false, /* l0_files_might_overlap */ true,
|
||||
CompactionReason::kUnknown,
|
||||
@@ -1171,7 +1175,8 @@ void CompactionPicker::UnregisterCompaction(Compaction* c) {
|
||||
|
||||
void CompactionPicker::PickFilesMarkedForCompaction(
|
||||
const std::string& cf_name, VersionStorageInfo* vstorage, int* start_level,
|
||||
int* output_level, CompactionInputFiles* start_level_inputs) {
|
||||
int* output_level, CompactionInputFiles* start_level_inputs,
|
||||
std::function<bool(const FileMetaData*)> skip_marked_file) {
|
||||
if (vstorage->FilesMarkedForCompaction().empty()) {
|
||||
return;
|
||||
}
|
||||
@@ -1181,6 +1186,9 @@ void CompactionPicker::PickFilesMarkedForCompaction(
|
||||
// If this assert() fails that means that some function marked some
|
||||
// files as being_compacted, but didn't call ComputeCompactionScore()
|
||||
assert(!level_file.second->being_compacted);
|
||||
if (skip_marked_file(level_file.second)) {
|
||||
return false;
|
||||
}
|
||||
*start_level = level_file.first;
|
||||
*output_level =
|
||||
(*start_level == 0) ? vstorage->base_level() : *start_level + 1;
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "db/compaction/compaction.h"
|
||||
#include "db/snapshot_checker.h"
|
||||
#include "db/version_set.h"
|
||||
#include "options/cf_options.h"
|
||||
#include "rocksdb/env.h"
|
||||
@@ -55,17 +56,17 @@ class CompactionPicker {
|
||||
// Returns nullptr if there is no compaction to be done.
|
||||
// Otherwise returns a pointer to a heap-allocated object that
|
||||
// describes the compaction. Caller should delete the result.
|
||||
virtual Compaction* PickCompaction(const std::string& cf_name,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
const MutableDBOptions& mutable_db_options,
|
||||
VersionStorageInfo* vstorage,
|
||||
LogBuffer* log_buffer) = 0;
|
||||
// Currently, only universal compaction will query existing snapshots and
|
||||
// pass it to aid compaction picking. And it's only passed when user-defined
|
||||
// timestamps is not enabled. The other compaction styles do not pass or use
|
||||
// `existing_snapshots` or `snapshot_checker`.
|
||||
virtual Compaction* PickCompaction(
|
||||
const std::string& cf_name, const MutableCFOptions& mutable_cf_options,
|
||||
const MutableDBOptions& mutable_db_options,
|
||||
const std::vector<SequenceNumber>& existing_snapshots,
|
||||
const SnapshotChecker* snapshot_checker, VersionStorageInfo* vstorage,
|
||||
LogBuffer* log_buffer) = 0;
|
||||
|
||||
// Return a compaction object for compacting the range [begin,end] in
|
||||
// the specified level. Returns nullptr if there is nothing in that
|
||||
// level that overlaps the specified range. Caller should delete
|
||||
// the result.
|
||||
//
|
||||
// The returned Compaction might not include the whole requested range.
|
||||
// In that case, compaction_end will be set to the next key that needs
|
||||
// compacting. In case the compaction will compact the whole range,
|
||||
@@ -203,10 +204,11 @@ class CompactionPicker {
|
||||
const CompactionInputFiles& output_level_inputs,
|
||||
std::vector<FileMetaData*>* grandparents);
|
||||
|
||||
void PickFilesMarkedForCompaction(const std::string& cf_name,
|
||||
VersionStorageInfo* vstorage,
|
||||
int* start_level, int* output_level,
|
||||
CompactionInputFiles* start_level_inputs);
|
||||
void PickFilesMarkedForCompaction(
|
||||
const std::string& cf_name, VersionStorageInfo* vstorage,
|
||||
int* start_level, int* output_level,
|
||||
CompactionInputFiles* start_level_inputs,
|
||||
std::function<bool(const FileMetaData*)> skip_marked_file);
|
||||
|
||||
bool GetOverlappingL0Files(VersionStorageInfo* vstorage,
|
||||
CompactionInputFiles* start_level_inputs,
|
||||
@@ -257,11 +259,13 @@ class NullCompactionPicker : public CompactionPicker {
|
||||
virtual ~NullCompactionPicker() {}
|
||||
|
||||
// Always return "nullptr"
|
||||
Compaction* PickCompaction(const std::string& /*cf_name*/,
|
||||
const MutableCFOptions& /*mutable_cf_options*/,
|
||||
const MutableDBOptions& /*mutable_db_options*/,
|
||||
VersionStorageInfo* /*vstorage*/,
|
||||
LogBuffer* /* log_buffer */) override {
|
||||
Compaction* PickCompaction(
|
||||
const std::string& /*cf_name*/,
|
||||
const MutableCFOptions& /*mutable_cf_options*/,
|
||||
const MutableDBOptions& /*mutable_db_options*/,
|
||||
const std::vector<SequenceNumber>& /*existing_snapshots*/,
|
||||
const SnapshotChecker* /*snapshot_checker*/,
|
||||
VersionStorageInfo* /*vstorage*/, LogBuffer* /* log_buffer */) override {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
@@ -118,7 +118,9 @@ Compaction* FIFOCompactionPicker::PickTTLCompaction(
|
||||
std::move(inputs), 0, 0, 0, 0, kNoCompression,
|
||||
mutable_cf_options.compression_opts,
|
||||
mutable_cf_options.default_write_temperature,
|
||||
/* max_subcompactions */ 0, {}, /* is manual */ false,
|
||||
/* max_subcompactions */ 0, {}, /* earliest_snapshot */ std::nullopt,
|
||||
/* snapshot_checker */ nullptr,
|
||||
/* is manual */ false,
|
||||
/* trim_ts */ "", vstorage->CompactionScore(0),
|
||||
/* is deletion compaction */ true, /* l0_files_might_overlap */ true,
|
||||
CompactionReason::kFIFOTtl);
|
||||
@@ -188,7 +190,9 @@ Compaction* FIFOCompactionPicker::PickSizeCompaction(
|
||||
0 /* output path ID */, mutable_cf_options.compression,
|
||||
mutable_cf_options.compression_opts,
|
||||
mutable_cf_options.default_write_temperature,
|
||||
0 /* max_subcompactions */, {}, /* is manual */ false,
|
||||
0 /* max_subcompactions */, {},
|
||||
/* earliest_snapshot */ std::nullopt,
|
||||
/* snapshot_checker */ nullptr, /* is manual */ false,
|
||||
/* trim_ts */ "", vstorage->CompactionScore(0),
|
||||
/* is deletion compaction */ false,
|
||||
/* l0_files_might_overlap */ true,
|
||||
@@ -284,7 +288,9 @@ Compaction* FIFOCompactionPicker::PickSizeCompaction(
|
||||
/* output_path_id */ 0, kNoCompression,
|
||||
mutable_cf_options.compression_opts,
|
||||
mutable_cf_options.default_write_temperature,
|
||||
/* max_subcompactions */ 0, {}, /* is manual */ false,
|
||||
/* max_subcompactions */ 0, {}, /* earliest_snapshot */ std::nullopt,
|
||||
/* snapshot_checker */ nullptr,
|
||||
/* is manual */ false,
|
||||
/* trim_ts */ "", vstorage->CompactionScore(0),
|
||||
/* is deletion compaction */ true,
|
||||
/* l0_files_might_overlap */ true, CompactionReason::kFIFOMaxSize);
|
||||
@@ -410,8 +416,9 @@ Compaction* FIFOCompactionPicker::PickTemperatureChangeCompaction(
|
||||
0 /* max compaction bytes, not applicable */, 0 /* output path ID */,
|
||||
mutable_cf_options.compression, mutable_cf_options.compression_opts,
|
||||
compaction_target_temp,
|
||||
/* max_subcompactions */ 0, {}, /* is manual */ false, /* trim_ts */ "",
|
||||
vstorage->CompactionScore(0),
|
||||
/* max_subcompactions */ 0, {}, /* earliest_snapshot */ std::nullopt,
|
||||
/* snapshot_checker */ nullptr,
|
||||
/* is manual */ false, /* trim_ts */ "", vstorage->CompactionScore(0),
|
||||
/* is deletion compaction */ false, /* l0_files_might_overlap */ true,
|
||||
CompactionReason::kChangeTemperature);
|
||||
return c;
|
||||
@@ -419,7 +426,9 @@ Compaction* FIFOCompactionPicker::PickTemperatureChangeCompaction(
|
||||
|
||||
Compaction* FIFOCompactionPicker::PickCompaction(
|
||||
const std::string& cf_name, const MutableCFOptions& mutable_cf_options,
|
||||
const MutableDBOptions& mutable_db_options, VersionStorageInfo* vstorage,
|
||||
const MutableDBOptions& mutable_db_options,
|
||||
const std::vector<SequenceNumber>& /* existing_snapshots */,
|
||||
const SnapshotChecker* /* snapshot_checker */, VersionStorageInfo* vstorage,
|
||||
LogBuffer* log_buffer) {
|
||||
Compaction* c = nullptr;
|
||||
if (mutable_cf_options.ttl > 0) {
|
||||
@@ -454,8 +463,10 @@ Compaction* FIFOCompactionPicker::CompactRange(
|
||||
assert(output_level == 0);
|
||||
*compaction_end = nullptr;
|
||||
LogBuffer log_buffer(InfoLogLevel::INFO_LEVEL, ioptions_.logger);
|
||||
Compaction* c = PickCompaction(cf_name, mutable_cf_options,
|
||||
mutable_db_options, vstorage, &log_buffer);
|
||||
Compaction* c =
|
||||
PickCompaction(cf_name, mutable_cf_options, mutable_db_options,
|
||||
/*existing_snapshots*/ {}, /*snapshot_checker*/ nullptr,
|
||||
vstorage, &log_buffer);
|
||||
log_buffer.FlushBufferToLog();
|
||||
return c;
|
||||
}
|
||||
|
||||
@@ -18,11 +18,12 @@ class FIFOCompactionPicker : public CompactionPicker {
|
||||
const InternalKeyComparator* icmp)
|
||||
: CompactionPicker(ioptions, icmp) {}
|
||||
|
||||
Compaction* PickCompaction(const std::string& cf_name,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
const MutableDBOptions& mutable_db_options,
|
||||
VersionStorageInfo* version,
|
||||
LogBuffer* log_buffer) override;
|
||||
Compaction* PickCompaction(
|
||||
const std::string& cf_name, const MutableCFOptions& mutable_cf_options,
|
||||
const MutableDBOptions& mutable_db_options,
|
||||
const std::vector<SequenceNumber>& /* existing_snapshots */,
|
||||
const SnapshotChecker* /* snapshot_checker */,
|
||||
VersionStorageInfo* version, LogBuffer* log_buffer) override;
|
||||
|
||||
Compaction* CompactRange(const std::string& cf_name,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
|
||||
@@ -262,7 +262,10 @@ void LevelCompactionBuilder::SetupInitialFiles() {
|
||||
parent_index_ = base_index_ = -1;
|
||||
|
||||
compaction_picker_->PickFilesMarkedForCompaction(
|
||||
cf_name_, vstorage_, &start_level_, &output_level_, &start_level_inputs_);
|
||||
cf_name_, vstorage_, &start_level_, &output_level_, &start_level_inputs_,
|
||||
/*skip_marked_file*/ [](const FileMetaData* /* file */) {
|
||||
return false;
|
||||
});
|
||||
if (!start_level_inputs_.empty()) {
|
||||
compaction_reason_ = CompactionReason::kFilesMarkedForCompaction;
|
||||
return;
|
||||
@@ -554,7 +557,9 @@ Compaction* LevelCompactionBuilder::GetCompaction() {
|
||||
vstorage_->base_level()),
|
||||
GetCompressionOptions(mutable_cf_options_, vstorage_, output_level_),
|
||||
mutable_cf_options_.default_write_temperature,
|
||||
/* max_subcompactions */ 0, std::move(grandparents_), is_manual_,
|
||||
/* max_subcompactions */ 0, std::move(grandparents_),
|
||||
/* earliest_snapshot */ std::nullopt, /* snapshot_checker */ nullptr,
|
||||
is_manual_,
|
||||
/* trim_ts */ "", start_level_score_, false /* deletion_compaction */,
|
||||
l0_files_might_overlap, compaction_reason_);
|
||||
|
||||
@@ -967,7 +972,9 @@ bool LevelCompactionBuilder::PickSizeBasedIntraL0Compaction() {
|
||||
|
||||
Compaction* LevelCompactionPicker::PickCompaction(
|
||||
const std::string& cf_name, const MutableCFOptions& mutable_cf_options,
|
||||
const MutableDBOptions& mutable_db_options, VersionStorageInfo* vstorage,
|
||||
const MutableDBOptions& mutable_db_options,
|
||||
const std::vector<SequenceNumber>& /*existing_snapshots */,
|
||||
const SnapshotChecker* /*snapshot_checker*/, VersionStorageInfo* vstorage,
|
||||
LogBuffer* log_buffer) {
|
||||
LevelCompactionBuilder builder(cf_name, vstorage, this, log_buffer,
|
||||
mutable_cf_options, ioptions_,
|
||||
|
||||
@@ -20,11 +20,12 @@ class LevelCompactionPicker : public CompactionPicker {
|
||||
LevelCompactionPicker(const ImmutableOptions& ioptions,
|
||||
const InternalKeyComparator* icmp)
|
||||
: CompactionPicker(ioptions, icmp) {}
|
||||
Compaction* PickCompaction(const std::string& cf_name,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
const MutableDBOptions& mutable_db_options,
|
||||
VersionStorageInfo* vstorage,
|
||||
LogBuffer* log_buffer) override;
|
||||
Compaction* PickCompaction(
|
||||
const std::string& cf_name, const MutableCFOptions& mutable_cf_options,
|
||||
const MutableDBOptions& mutable_db_options,
|
||||
const std::vector<SequenceNumber>& /* existing_snapshots */,
|
||||
const SnapshotChecker* /* snapshot_checker */,
|
||||
VersionStorageInfo* vstorage, LogBuffer* log_buffer) override;
|
||||
|
||||
bool NeedsCompaction(const VersionStorageInfo* vstorage) const override;
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -35,7 +35,9 @@ class UniversalCompactionBuilder {
|
||||
UniversalCompactionBuilder(
|
||||
const ImmutableOptions& ioptions, const InternalKeyComparator* icmp,
|
||||
const std::string& cf_name, const MutableCFOptions& mutable_cf_options,
|
||||
const MutableDBOptions& mutable_db_options, VersionStorageInfo* vstorage,
|
||||
const MutableDBOptions& mutable_db_options,
|
||||
const std::vector<SequenceNumber>& existing_snapshots,
|
||||
const SnapshotChecker* snapshot_checker, VersionStorageInfo* vstorage,
|
||||
UniversalCompactionPicker* picker, LogBuffer* log_buffer)
|
||||
: ioptions_(ioptions),
|
||||
icmp_(icmp),
|
||||
@@ -44,7 +46,19 @@ class UniversalCompactionBuilder {
|
||||
mutable_db_options_(mutable_db_options),
|
||||
vstorage_(vstorage),
|
||||
picker_(picker),
|
||||
log_buffer_(log_buffer) {}
|
||||
log_buffer_(log_buffer) {
|
||||
assert(icmp_);
|
||||
const auto* ucmp = icmp_->user_comparator();
|
||||
assert(ucmp);
|
||||
// These parameters are only passed when user-defined timestamp is not
|
||||
// enabled.
|
||||
if (ucmp->timestamp_size() == 0) {
|
||||
earliest_snapshot_ = existing_snapshots.empty()
|
||||
? kMaxSequenceNumber
|
||||
: existing_snapshots.at(0);
|
||||
snapshot_checker_ = snapshot_checker;
|
||||
}
|
||||
}
|
||||
|
||||
// Form and return the compaction object. The caller owns return object.
|
||||
Compaction* PickCompaction();
|
||||
@@ -52,12 +66,15 @@ class UniversalCompactionBuilder {
|
||||
private:
|
||||
struct SortedRun {
|
||||
SortedRun(int _level, FileMetaData* _file, uint64_t _size,
|
||||
uint64_t _compensated_file_size, bool _being_compacted)
|
||||
uint64_t _compensated_file_size, bool _being_compacted,
|
||||
bool _level_has_marked_standalone_rangedel)
|
||||
: level(_level),
|
||||
file(_file),
|
||||
size(_size),
|
||||
compensated_file_size(_compensated_file_size),
|
||||
being_compacted(_being_compacted) {
|
||||
being_compacted(_being_compacted),
|
||||
level_has_marked_standalone_rangedel(
|
||||
_level_has_marked_standalone_rangedel) {
|
||||
assert(compensated_file_size > 0);
|
||||
assert(level != 0 || file != nullptr);
|
||||
}
|
||||
@@ -79,6 +96,10 @@ class UniversalCompactionBuilder {
|
||||
uint64_t size;
|
||||
uint64_t compensated_file_size;
|
||||
bool being_compacted;
|
||||
// True if this level has any file that is a standalone range deletion file
|
||||
// marked for compaction. Best effort is made to make only deletion
|
||||
// triggered compaction pick this type of file.
|
||||
bool level_has_marked_standalone_rangedel;
|
||||
};
|
||||
|
||||
// Pick Universal compaction to limit read amplification
|
||||
@@ -98,6 +119,11 @@ class UniversalCompactionBuilder {
|
||||
|
||||
Compaction* PickDeleteTriggeredCompaction();
|
||||
|
||||
// Returns true if this given file (that is marked be compaction) should be
|
||||
// skipped from being picked for now. We do this to best use standalone range
|
||||
// tombstone files.
|
||||
bool ShouldSkipMarkedFile(const FileMetaData* file) const;
|
||||
|
||||
// Form a compaction from the sorted run indicated by start_index to the
|
||||
// oldest sorted run.
|
||||
// The caller is responsible for making sure that those files are not in
|
||||
@@ -234,8 +260,18 @@ class UniversalCompactionBuilder {
|
||||
VersionStorageInfo* vstorage_;
|
||||
UniversalCompactionPicker* picker_;
|
||||
LogBuffer* log_buffer_;
|
||||
// Optional earliest snapshot at time of compaction picking. This is only
|
||||
// provided if the column family doesn't enable user-defined timestamps.
|
||||
// And this information is only passed to `Compaction` picked by deletion
|
||||
// triggered compaction for possible optimizations.
|
||||
std::optional<SequenceNumber> earliest_snapshot_;
|
||||
const SnapshotChecker* snapshot_checker_;
|
||||
// Mapping from file id to its index in the sorted run for the files that are
|
||||
// marked for compaction. This is only populated when snapshot info is
|
||||
// populated.
|
||||
std::map<uint64_t, size_t> file_marked_for_compaction_to_sorted_run_index_;
|
||||
|
||||
static std::vector<UniversalCompactionBuilder::SortedRun> CalculateSortedRuns(
|
||||
std::vector<UniversalCompactionBuilder::SortedRun> CalculateSortedRuns(
|
||||
const VersionStorageInfo& vstorage, int last_level,
|
||||
uint64_t* max_run_size);
|
||||
|
||||
@@ -394,11 +430,13 @@ bool UniversalCompactionPicker::NeedsCompaction(
|
||||
|
||||
Compaction* UniversalCompactionPicker::PickCompaction(
|
||||
const std::string& cf_name, const MutableCFOptions& mutable_cf_options,
|
||||
const MutableDBOptions& mutable_db_options, VersionStorageInfo* vstorage,
|
||||
const MutableDBOptions& mutable_db_options,
|
||||
const std::vector<SequenceNumber>& existing_snapshots,
|
||||
const SnapshotChecker* snapshot_checker, VersionStorageInfo* vstorage,
|
||||
LogBuffer* log_buffer) {
|
||||
UniversalCompactionBuilder builder(ioptions_, icmp_, cf_name,
|
||||
mutable_cf_options, mutable_db_options,
|
||||
vstorage, this, log_buffer);
|
||||
UniversalCompactionBuilder builder(
|
||||
ioptions_, icmp_, cf_name, mutable_cf_options, mutable_db_options,
|
||||
existing_snapshots, snapshot_checker, vstorage, this, log_buffer);
|
||||
return builder.PickCompaction();
|
||||
}
|
||||
|
||||
@@ -448,14 +486,20 @@ UniversalCompactionBuilder::CalculateSortedRuns(
|
||||
*max_run_size = 0;
|
||||
std::vector<UniversalCompactionBuilder::SortedRun> ret;
|
||||
for (FileMetaData* f : vstorage.LevelFiles(0)) {
|
||||
ret.emplace_back(0, f, f->fd.GetFileSize(), f->compensated_file_size,
|
||||
f->being_compacted);
|
||||
if (earliest_snapshot_.has_value() && f->marked_for_compaction) {
|
||||
file_marked_for_compaction_to_sorted_run_index_.emplace(f->fd.GetNumber(),
|
||||
ret.size());
|
||||
}
|
||||
ret.emplace_back(
|
||||
0, f, f->fd.GetFileSize(), f->compensated_file_size, f->being_compacted,
|
||||
f->marked_for_compaction && f->FileIsStandAloneRangeTombstone());
|
||||
*max_run_size = std::max(*max_run_size, f->fd.GetFileSize());
|
||||
}
|
||||
for (int level = 1; level <= last_level; level++) {
|
||||
uint64_t total_compensated_size = 0U;
|
||||
uint64_t total_size = 0U;
|
||||
bool being_compacted = false;
|
||||
bool level_has_marked_standalone_rangedel = false;
|
||||
for (FileMetaData* f : vstorage.LevelFiles(level)) {
|
||||
total_compensated_size += f->compensated_file_size;
|
||||
total_size += f->fd.GetFileSize();
|
||||
@@ -467,16 +511,57 @@ UniversalCompactionBuilder::CalculateSortedRuns(
|
||||
if (f->being_compacted) {
|
||||
being_compacted = f->being_compacted;
|
||||
}
|
||||
level_has_marked_standalone_rangedel =
|
||||
level_has_marked_standalone_rangedel ||
|
||||
(f->marked_for_compaction && f->FileIsStandAloneRangeTombstone());
|
||||
if (earliest_snapshot_.has_value() && f->marked_for_compaction) {
|
||||
file_marked_for_compaction_to_sorted_run_index_.emplace(
|
||||
f->fd.GetNumber(), ret.size());
|
||||
}
|
||||
}
|
||||
if (total_compensated_size > 0) {
|
||||
ret.emplace_back(level, nullptr, total_size, total_compensated_size,
|
||||
being_compacted);
|
||||
being_compacted, level_has_marked_standalone_rangedel);
|
||||
}
|
||||
*max_run_size = std::max(*max_run_size, total_size);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool UniversalCompactionBuilder::ShouldSkipMarkedFile(
|
||||
const FileMetaData* file) const {
|
||||
assert(file->marked_for_compaction);
|
||||
if (!earliest_snapshot_.has_value()) {
|
||||
return false;
|
||||
}
|
||||
if (!file->FileIsStandAloneRangeTombstone()) {
|
||||
return false;
|
||||
}
|
||||
// Skip until earliest snapshot advances at or above this standalone range
|
||||
// tombstone file. `DB::ReleaseSnapshot` will re-examine and schedule
|
||||
// compaction for it.
|
||||
if (!DataIsDefinitelyInSnapshot(file->fd.largest_seqno,
|
||||
earliest_snapshot_.value(),
|
||||
snapshot_checker_)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
auto iter = file_marked_for_compaction_to_sorted_run_index_.find(
|
||||
file->fd.GetNumber());
|
||||
assert(iter != file_marked_for_compaction_to_sorted_run_index_.end());
|
||||
size_t idx = iter->second;
|
||||
const SortedRun* succeeding_sorted_run =
|
||||
idx < sorted_runs_.size() - 1 ? &sorted_runs_[idx + 1] : nullptr;
|
||||
// Marked standalone range tombstone file is best used if it's in the start
|
||||
// input level. Skip to let that compaction happen first.
|
||||
if (succeeding_sorted_run &&
|
||||
succeeding_sorted_run->level_has_marked_standalone_rangedel) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Universal style of compaction. Pick files that are contiguous in
|
||||
// time-range to compact.
|
||||
Compaction* UniversalCompactionBuilder::PickCompaction() {
|
||||
@@ -580,7 +665,8 @@ Compaction* UniversalCompactionBuilder::PickCompaction() {
|
||||
// Get the total number of sorted runs that are not being compacted
|
||||
int num_sr_not_compacted = 0;
|
||||
for (size_t i = 0; i < sorted_runs_.size(); i++) {
|
||||
if (sorted_runs_[i].being_compacted == false) {
|
||||
if (sorted_runs_[i].being_compacted == false &&
|
||||
!sorted_runs_[i].level_has_marked_standalone_rangedel) {
|
||||
num_sr_not_compacted++;
|
||||
}
|
||||
}
|
||||
@@ -743,16 +829,24 @@ Compaction* UniversalCompactionBuilder::PickCompactionToReduceSortedRuns(
|
||||
for (sr = nullptr; loop < sorted_runs_.size(); loop++) {
|
||||
sr = &sorted_runs_[loop];
|
||||
|
||||
if (!sr->being_compacted) {
|
||||
if (!sr->being_compacted && !sr->level_has_marked_standalone_rangedel) {
|
||||
candidate_count = 1;
|
||||
break;
|
||||
}
|
||||
char file_num_buf[kFormatFileNumberBufSize];
|
||||
sr->Dump(file_num_buf, sizeof(file_num_buf));
|
||||
ROCKS_LOG_BUFFER(log_buffer_,
|
||||
"[%s] Universal: %s"
|
||||
"[%d] being compacted, skipping",
|
||||
cf_name_.c_str(), file_num_buf, loop);
|
||||
if (sr->being_compacted) {
|
||||
ROCKS_LOG_BUFFER(log_buffer_,
|
||||
"[%s] Universal: %s"
|
||||
"[%d] being compacted, skipping",
|
||||
cf_name_.c_str(), file_num_buf, loop);
|
||||
} else if (sr->level_has_marked_standalone_rangedel) {
|
||||
ROCKS_LOG_BUFFER(log_buffer_,
|
||||
"[%s] Universal: %s"
|
||||
"[%d] has standalone range tombstone files marked for "
|
||||
"compaction, skipping",
|
||||
cf_name_.c_str(), file_num_buf, loop);
|
||||
}
|
||||
|
||||
sr = nullptr;
|
||||
}
|
||||
@@ -773,7 +867,8 @@ Compaction* UniversalCompactionBuilder::PickCompactionToReduceSortedRuns(
|
||||
candidate_count < max_files_to_compact && i < sorted_runs_.size();
|
||||
i++) {
|
||||
const SortedRun* succeeding_sr = &sorted_runs_[i];
|
||||
if (succeeding_sr->being_compacted) {
|
||||
if (succeeding_sr->being_compacted ||
|
||||
succeeding_sr->level_has_marked_standalone_rangedel) {
|
||||
break;
|
||||
}
|
||||
// Pick files if the total/last candidate file size (increased by the
|
||||
@@ -923,6 +1018,8 @@ Compaction* UniversalCompactionBuilder::PickCompactionToReduceSortedRuns(
|
||||
output_level, enable_compression),
|
||||
mutable_cf_options_.default_write_temperature,
|
||||
/* max_subcompactions */ 0, grandparents,
|
||||
/* earliest_snapshot */ std::nullopt,
|
||||
/* snapshot_checker */ nullptr,
|
||||
/* is manual */ false, /* trim_ts */ "", score_,
|
||||
false /* deletion_compaction */,
|
||||
/* l0_files_might_overlap */ true, compaction_reason);
|
||||
@@ -939,7 +1036,8 @@ Compaction* UniversalCompactionBuilder::PickCompactionToReduceSizeAmp() {
|
||||
const size_t end_index = ShouldSkipLastSortedRunForSizeAmpCompaction()
|
||||
? sorted_runs_.size() - 2
|
||||
: sorted_runs_.size() - 1;
|
||||
if (sorted_runs_[end_index].being_compacted) {
|
||||
if (sorted_runs_[end_index].being_compacted ||
|
||||
sorted_runs_[end_index].level_has_marked_standalone_rangedel) {
|
||||
return nullptr;
|
||||
}
|
||||
const uint64_t base_sr_size = sorted_runs_[end_index].size;
|
||||
@@ -950,14 +1048,23 @@ Compaction* UniversalCompactionBuilder::PickCompactionToReduceSizeAmp() {
|
||||
// Get longest span (i.e, [start_index, end_index]) of available sorted runs
|
||||
while (start_index > 0) {
|
||||
const SortedRun* sr = &sorted_runs_[start_index - 1];
|
||||
if (sr->being_compacted) {
|
||||
if (sr->being_compacted || sr->level_has_marked_standalone_rangedel) {
|
||||
char file_num_buf[kFormatFileNumberBufSize];
|
||||
sr->Dump(file_num_buf, sizeof(file_num_buf), true);
|
||||
ROCKS_LOG_BUFFER(
|
||||
log_buffer_,
|
||||
"[%s] Universal: stopping at sorted run undergoing compaction: "
|
||||
"%s[%" ROCKSDB_PRIszt "]",
|
||||
cf_name_.c_str(), file_num_buf, start_index - 1);
|
||||
if (sr->being_compacted) {
|
||||
ROCKS_LOG_BUFFER(
|
||||
log_buffer_,
|
||||
"[%s] Universal: stopping at sorted run undergoing compaction: "
|
||||
"%s[%" ROCKSDB_PRIszt "]",
|
||||
cf_name_.c_str(), file_num_buf, start_index - 1);
|
||||
} else if (sr->level_has_marked_standalone_rangedel) {
|
||||
ROCKS_LOG_BUFFER(
|
||||
log_buffer_,
|
||||
"[%s] Universal: stopping at sorted run that has standalone range "
|
||||
"tombstone files marked for compaction: "
|
||||
"%s[%" ROCKSDB_PRIszt "]",
|
||||
cf_name_.c_str(), file_num_buf, start_index - 1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
candidate_size += sr->compensated_file_size;
|
||||
@@ -1257,7 +1364,10 @@ Compaction* UniversalCompactionBuilder::PickIncrementalForReduceSizeAmp(
|
||||
GetCompressionOptions(mutable_cf_options_, vstorage_, output_level,
|
||||
true /* enable_compression */),
|
||||
mutable_cf_options_.default_write_temperature,
|
||||
/* max_subcompactions */ 0, /* grandparents */ {}, /* is manual */ false,
|
||||
/* max_subcompactions */ 0, /* grandparents */ {},
|
||||
/* earliest_snapshot */ std::nullopt,
|
||||
/* snapshot_checker */ nullptr,
|
||||
/* is manual */ false,
|
||||
/* trim_ts */ "", score_, false /* deletion_compaction */,
|
||||
/* l0_files_might_overlap */ true,
|
||||
CompactionReason::kUniversalSizeAmplification);
|
||||
@@ -1288,7 +1398,7 @@ Compaction* UniversalCompactionBuilder::PickDeleteTriggeredCompaction() {
|
||||
continue;
|
||||
}
|
||||
FileMetaData* f = vstorage_->LevelFiles(0)[loop];
|
||||
if (f->marked_for_compaction) {
|
||||
if (f->marked_for_compaction && !ShouldSkipMarkedFile(f)) {
|
||||
start_level_inputs.files.push_back(f);
|
||||
start_index =
|
||||
static_cast<int>(loop); // Consider this as the first candidate.
|
||||
@@ -1302,7 +1412,7 @@ Compaction* UniversalCompactionBuilder::PickDeleteTriggeredCompaction() {
|
||||
|
||||
for (size_t loop = start_index + 1; loop < sorted_runs_.size(); loop++) {
|
||||
SortedRun* sr = &sorted_runs_[loop];
|
||||
if (sr->being_compacted) {
|
||||
if (sr->being_compacted || sr->level_has_marked_standalone_rangedel) {
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1321,7 +1431,10 @@ Compaction* UniversalCompactionBuilder::PickDeleteTriggeredCompaction() {
|
||||
// leveled. We pick one of the files marked for compaction and compact with
|
||||
// overlapping files in the adjacent level.
|
||||
picker_->PickFilesMarkedForCompaction(cf_name_, vstorage_, &start_level,
|
||||
&output_level, &start_level_inputs);
|
||||
&output_level, &start_level_inputs,
|
||||
[this](const FileMetaData* file) {
|
||||
return ShouldSkipMarkedFile(file);
|
||||
});
|
||||
if (start_level_inputs.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
@@ -1401,7 +1514,9 @@ Compaction* UniversalCompactionBuilder::PickDeleteTriggeredCompaction() {
|
||||
GetCompressionType(vstorage_, mutable_cf_options_, output_level, 1),
|
||||
GetCompressionOptions(mutable_cf_options_, vstorage_, output_level),
|
||||
mutable_cf_options_.default_write_temperature,
|
||||
/* max_subcompactions */ 0, grandparents, /* is manual */ false,
|
||||
/* max_subcompactions */ 0, grandparents, earliest_snapshot_,
|
||||
snapshot_checker_,
|
||||
/* is manual */ false,
|
||||
/* trim_ts */ "", score_, false /* deletion_compaction */,
|
||||
/* l0_files_might_overlap */ true,
|
||||
CompactionReason::kFilesMarkedForCompaction);
|
||||
@@ -1494,7 +1609,10 @@ Compaction* UniversalCompactionBuilder::PickCompactionWithSortedRunRange(
|
||||
GetCompressionOptions(mutable_cf_options_, vstorage_, output_level,
|
||||
true /* enable_compression */),
|
||||
mutable_cf_options_.default_write_temperature,
|
||||
/* max_subcompactions */ 0, /* grandparents */ {}, /* is manual */ false,
|
||||
/* max_subcompactions */ 0, /* grandparents */ {},
|
||||
/* earliest_snapshot */ std::nullopt,
|
||||
/* snapshot_checker */ nullptr,
|
||||
/* is manual */ false,
|
||||
/* trim_ts */ "", score_, false /* deletion_compaction */,
|
||||
/* l0_files_might_overlap */ true, compaction_reason);
|
||||
}
|
||||
@@ -1515,7 +1633,8 @@ Compaction* UniversalCompactionBuilder::PickPeriodicCompaction() {
|
||||
// included in the compaction.
|
||||
|
||||
size_t start_index = sorted_runs_.size();
|
||||
while (start_index > 0 && !sorted_runs_[start_index - 1].being_compacted) {
|
||||
while (start_index > 0 && !sorted_runs_[start_index - 1].being_compacted &&
|
||||
!sorted_runs_[start_index - 1].level_has_marked_standalone_rangedel) {
|
||||
start_index--;
|
||||
}
|
||||
if (start_index == sorted_runs_.size()) {
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "db/compaction/compaction_picker.h"
|
||||
#include "db/snapshot_checker.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
class UniversalCompactionPicker : public CompactionPicker {
|
||||
@@ -17,11 +18,12 @@ class UniversalCompactionPicker : public CompactionPicker {
|
||||
UniversalCompactionPicker(const ImmutableOptions& ioptions,
|
||||
const InternalKeyComparator* icmp)
|
||||
: CompactionPicker(ioptions, icmp) {}
|
||||
Compaction* PickCompaction(const std::string& cf_name,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
const MutableDBOptions& mutable_db_options,
|
||||
VersionStorageInfo* vstorage,
|
||||
LogBuffer* log_buffer) override;
|
||||
Compaction* PickCompaction(
|
||||
const std::string& cf_name, const MutableCFOptions& mutable_cf_options,
|
||||
const MutableDBOptions& mutable_db_options,
|
||||
const std::vector<SequenceNumber>& existing_snapshots,
|
||||
const SnapshotChecker* snapshot_checker, VersionStorageInfo* vstorage,
|
||||
LogBuffer* log_buffer) override;
|
||||
int MaxOutputLevel() const override { return NumberLevels() - 1; }
|
||||
|
||||
bool NeedsCompaction(const VersionStorageInfo* vstorage) const override;
|
||||
|
||||
@@ -48,6 +48,14 @@ CompactionJob::ProcessKeyValueCompactionWithCompactionService(
|
||||
compaction_input.has_end = sub_compact->end.has_value();
|
||||
compaction_input.end =
|
||||
compaction_input.has_end ? sub_compact->end->ToString() : "";
|
||||
compaction_input.options_file_number =
|
||||
sub_compact->compaction->input_version()
|
||||
->version_set()
|
||||
->options_file_number();
|
||||
|
||||
TEST_SYNC_POINT_CALLBACK(
|
||||
"CompactionServiceJob::ProcessKeyValueCompactionWithCompactionService",
|
||||
&compaction_input);
|
||||
|
||||
std::string compaction_input_binary;
|
||||
Status s = compaction_input.Write(&compaction_input_binary);
|
||||
@@ -68,8 +76,11 @@ CompactionJob::ProcessKeyValueCompactionWithCompactionService(
|
||||
"[%s] [JOB %d] Starting remote compaction (output level: %d): %s",
|
||||
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) {
|
||||
@@ -192,6 +203,8 @@ CompactionJob::ProcessKeyValueCompactionWithCompactionService(
|
||||
meta.oldest_ancester_time = file.oldest_ancester_time;
|
||||
meta.file_creation_time = file.file_creation_time;
|
||||
meta.epoch_number = file.epoch_number;
|
||||
meta.file_checksum = file.file_checksum;
|
||||
meta.file_checksum_func_name = file.file_checksum_func_name;
|
||||
meta.marked_for_compaction = file.marked_for_compaction;
|
||||
meta.unique_id = file.unique_id;
|
||||
|
||||
@@ -199,11 +212,14 @@ CompactionJob::ProcessKeyValueCompactionWithCompactionService(
|
||||
sub_compact->Current().AddOutput(std::move(meta),
|
||||
cfd->internal_comparator(), false, true,
|
||||
file.paranoid_hash);
|
||||
sub_compact->Current().UpdateTableProperties(file.table_properties);
|
||||
}
|
||||
sub_compact->compaction_job_stats = compaction_result.stats;
|
||||
sub_compact->Current().SetNumOutputRecords(
|
||||
compaction_result.num_output_records);
|
||||
sub_compact->Current().SetTotalBytes(compaction_result.total_bytes);
|
||||
compaction_result.stats.num_output_records);
|
||||
sub_compact->Current().SetNumOutputFiles(
|
||||
compaction_result.stats.num_output_files);
|
||||
sub_compact->Current().AddBytesWritten(compaction_result.bytes_written);
|
||||
RecordTick(stats_, REMOTE_COMPACT_READ_BYTES, compaction_result.bytes_read);
|
||||
RecordTick(stats_, REMOTE_COMPACT_WRITE_BYTES,
|
||||
compaction_result.bytes_written);
|
||||
@@ -223,6 +239,18 @@ void CompactionServiceCompactionJob::RecordCompactionIOStats() {
|
||||
CompactionJob::RecordCompactionIOStats();
|
||||
}
|
||||
|
||||
void CompactionServiceCompactionJob::UpdateCompactionJobStats(
|
||||
const InternalStats::CompactionStats& stats) const {
|
||||
compaction_job_stats_->elapsed_micros = stats.micros;
|
||||
|
||||
// output information only in remote compaction
|
||||
compaction_job_stats_->total_output_bytes = stats.bytes_written;
|
||||
compaction_job_stats_->total_output_bytes_blob = stats.bytes_written_blob;
|
||||
compaction_job_stats_->num_output_records = stats.num_output_records;
|
||||
compaction_job_stats_->num_output_files = stats.num_output_files;
|
||||
compaction_job_stats_->num_output_files_blob = stats.num_output_files_blob;
|
||||
}
|
||||
|
||||
CompactionServiceCompactionJob::CompactionServiceCompactionJob(
|
||||
int job_id, Compaction* compaction, const ImmutableDBOptions& db_options,
|
||||
const MutableDBOptions& mutable_db_options, const FileOptions& file_options,
|
||||
@@ -277,6 +305,9 @@ Status CompactionServiceCompactionJob::Run() {
|
||||
|
||||
log_buffer_->FlushBufferToLog();
|
||||
LogCompaction();
|
||||
|
||||
compaction_result_->stats.Reset();
|
||||
|
||||
const uint64_t start_micros = db_options_.clock->NowMicros();
|
||||
c->GetOrInitInputTableProperties();
|
||||
|
||||
@@ -317,20 +348,32 @@ Status CompactionServiceCompactionJob::Run() {
|
||||
if (status.ok()) {
|
||||
status = io_s;
|
||||
}
|
||||
if (status.ok()) {
|
||||
// TODO: Add verify_table()
|
||||
}
|
||||
|
||||
// Finish up all book-keeping to unify the subcompaction results
|
||||
compact_->AggregateCompactionStats(compaction_stats_, *compaction_job_stats_);
|
||||
UpdateCompactionStats();
|
||||
RecordCompactionIOStats();
|
||||
|
||||
LogFlush(db_options_.info_log);
|
||||
compact_->status = status;
|
||||
compact_->status.PermitUncheckedError();
|
||||
|
||||
// Build compaction result
|
||||
// Build Compaction Job Stats
|
||||
|
||||
// 1. Aggregate CompactionOutputStats into Internal Compaction Stats
|
||||
// (compaction_stats_) and aggregate Compaction Job Stats
|
||||
// (compaction_job_stats_) from the sub compactions
|
||||
compact_->AggregateCompactionStats(compaction_stats_, *compaction_job_stats_);
|
||||
|
||||
// 2. Update the Output information in the Compaction Job Stats with
|
||||
// aggregated Internal Compaction Stats.
|
||||
UpdateCompactionJobStats(compaction_stats_.stats);
|
||||
|
||||
// 3. Set fields that are not propagated as part of aggregations above
|
||||
compaction_result_->stats.is_manual_compaction = c->is_manual_compaction();
|
||||
compaction_result_->stats.is_full_compaction = c->is_full_compaction();
|
||||
compaction_result_->stats.is_remote_compaction = true;
|
||||
|
||||
// 4. Update IO Stats that are not part of the aggregations above (bytes_read,
|
||||
// bytes_written)
|
||||
RecordCompactionIOStats();
|
||||
|
||||
// Build Output
|
||||
compaction_result_->output_level = compact_->compaction->output_level();
|
||||
compaction_result_->output_path = output_path_;
|
||||
for (const auto& output_file : sub_compact->GetOutputs()) {
|
||||
@@ -339,16 +382,14 @@ Status CompactionServiceCompactionJob::Run() {
|
||||
MakeTableFileName(meta.fd.GetNumber()), meta.fd.smallest_seqno,
|
||||
meta.fd.largest_seqno, meta.smallest.Encode().ToString(),
|
||||
meta.largest.Encode().ToString(), meta.oldest_ancester_time,
|
||||
meta.file_creation_time, meta.epoch_number,
|
||||
output_file.validator.GetHash(), meta.marked_for_compaction,
|
||||
meta.unique_id);
|
||||
meta.file_creation_time, meta.epoch_number, meta.file_checksum,
|
||||
meta.file_checksum_func_name, output_file.validator.GetHash(),
|
||||
meta.marked_for_compaction, meta.unique_id,
|
||||
output_file.table_properties);
|
||||
}
|
||||
InternalStats::CompactionStatsFull compaction_stats;
|
||||
sub_compact->AggregateCompactionStats(compaction_stats);
|
||||
compaction_result_->num_output_records =
|
||||
compaction_stats.stats.num_output_records;
|
||||
compaction_result_->total_bytes = compaction_stats.TotalBytesWritten();
|
||||
|
||||
TEST_SYNC_POINT_CALLBACK("CompactionServiceCompactionJob::Run:0",
|
||||
&compaction_result_);
|
||||
return status;
|
||||
}
|
||||
|
||||
@@ -431,6 +472,163 @@ static std::unordered_map<std::string, OptionTypeInfo> cs_input_type_info = {
|
||||
{"end",
|
||||
{offsetof(struct CompactionServiceInput, end), OptionType::kEncodedString,
|
||||
OptionVerificationType::kNormal, OptionTypeFlags::kNone}},
|
||||
{"options_file_number",
|
||||
{offsetof(struct CompactionServiceInput, options_file_number),
|
||||
OptionType::kUInt64T, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone}},
|
||||
};
|
||||
static std::unordered_map<std::string, OptionTypeInfo>
|
||||
table_properties_type_info = {
|
||||
{"orig_file_number",
|
||||
{offsetof(struct TableProperties, orig_file_number),
|
||||
OptionType::kUInt64T, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone}},
|
||||
{"data_size",
|
||||
{offsetof(struct TableProperties, data_size), OptionType::kUInt64T,
|
||||
OptionVerificationType::kNormal, OptionTypeFlags::kNone}},
|
||||
{"index_size",
|
||||
{offsetof(struct TableProperties, index_size), OptionType::kUInt64T,
|
||||
OptionVerificationType::kNormal, OptionTypeFlags::kNone}},
|
||||
{"index_partitions",
|
||||
{offsetof(struct TableProperties, index_partitions),
|
||||
OptionType::kUInt64T, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone}},
|
||||
{"top_level_index_size",
|
||||
{offsetof(struct TableProperties, top_level_index_size),
|
||||
OptionType::kUInt64T, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone}},
|
||||
{"index_key_is_user_key",
|
||||
{offsetof(struct TableProperties, index_key_is_user_key),
|
||||
OptionType::kUInt64T, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone}},
|
||||
{"index_value_is_delta_encoded",
|
||||
{offsetof(struct TableProperties, index_value_is_delta_encoded),
|
||||
OptionType::kUInt64T, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone}},
|
||||
{"filter_size",
|
||||
{offsetof(struct TableProperties, filter_size), OptionType::kUInt64T,
|
||||
OptionVerificationType::kNormal, OptionTypeFlags::kNone}},
|
||||
{"raw_key_size",
|
||||
{offsetof(struct TableProperties, raw_key_size), OptionType::kUInt64T,
|
||||
OptionVerificationType::kNormal, OptionTypeFlags::kNone}},
|
||||
{"raw_value_size",
|
||||
{offsetof(struct TableProperties, raw_value_size),
|
||||
OptionType::kUInt64T, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone}},
|
||||
{"num_data_blocks",
|
||||
{offsetof(struct TableProperties, num_data_blocks),
|
||||
OptionType::kUInt64T, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone}},
|
||||
{"num_entries",
|
||||
{offsetof(struct TableProperties, num_entries), OptionType::kUInt64T,
|
||||
OptionVerificationType::kNormal, OptionTypeFlags::kNone}},
|
||||
{"num_filter_entries",
|
||||
{offsetof(struct TableProperties, num_filter_entries),
|
||||
OptionType::kUInt64T, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone}},
|
||||
{"num_deletions",
|
||||
{offsetof(struct TableProperties, num_deletions), OptionType::kUInt64T,
|
||||
OptionVerificationType::kNormal, OptionTypeFlags::kNone}},
|
||||
{"num_merge_operands",
|
||||
{offsetof(struct TableProperties, num_merge_operands),
|
||||
OptionType::kUInt64T, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone}},
|
||||
{"num_range_deletions",
|
||||
{offsetof(struct TableProperties, num_range_deletions),
|
||||
OptionType::kUInt64T, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone}},
|
||||
{"format_version",
|
||||
{offsetof(struct TableProperties, format_version),
|
||||
OptionType::kUInt64T, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone}},
|
||||
{"fixed_key_len",
|
||||
{offsetof(struct TableProperties, fixed_key_len), OptionType::kUInt64T,
|
||||
OptionVerificationType::kNormal, OptionTypeFlags::kNone}},
|
||||
{"column_family_id",
|
||||
{offsetof(struct TableProperties, column_family_id),
|
||||
OptionType::kUInt64T, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone}},
|
||||
{"creation_time",
|
||||
{offsetof(struct TableProperties, creation_time), OptionType::kUInt64T,
|
||||
OptionVerificationType::kNormal, OptionTypeFlags::kNone}},
|
||||
{"oldest_key_time",
|
||||
{offsetof(struct TableProperties, oldest_key_time),
|
||||
OptionType::kUInt64T, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone}},
|
||||
{"file_creation_time",
|
||||
{offsetof(struct TableProperties, file_creation_time),
|
||||
OptionType::kUInt64T, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone}},
|
||||
{"slow_compression_estimated_data_size",
|
||||
{offsetof(struct TableProperties,
|
||||
slow_compression_estimated_data_size),
|
||||
OptionType::kUInt64T, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone}},
|
||||
{"fast_compression_estimated_data_size",
|
||||
{offsetof(struct TableProperties,
|
||||
fast_compression_estimated_data_size),
|
||||
OptionType::kUInt64T, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone}},
|
||||
{"external_sst_file_global_seqno_offset",
|
||||
{offsetof(struct TableProperties,
|
||||
external_sst_file_global_seqno_offset),
|
||||
OptionType::kUInt64T, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone}},
|
||||
{"tail_start_offset",
|
||||
{offsetof(struct TableProperties, tail_start_offset),
|
||||
OptionType::kUInt64T, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone}},
|
||||
{"user_defined_timestamps_persisted",
|
||||
{offsetof(struct TableProperties, user_defined_timestamps_persisted),
|
||||
OptionType::kUInt64T, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone}},
|
||||
{"key_largest_seqno",
|
||||
{offsetof(struct TableProperties, key_largest_seqno),
|
||||
OptionType::kUInt64T, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone}},
|
||||
{"db_id",
|
||||
{offsetof(struct TableProperties, db_id), OptionType::kEncodedString}},
|
||||
{"db_session_id",
|
||||
{offsetof(struct TableProperties, db_session_id),
|
||||
OptionType::kEncodedString}},
|
||||
{"db_host_id",
|
||||
{offsetof(struct TableProperties, db_host_id),
|
||||
OptionType::kEncodedString}},
|
||||
{"column_family_name",
|
||||
{offsetof(struct TableProperties, column_family_name),
|
||||
OptionType::kEncodedString}},
|
||||
{"filter_policy_name",
|
||||
{offsetof(struct TableProperties, filter_policy_name),
|
||||
OptionType::kEncodedString}},
|
||||
{"comparator_name",
|
||||
{offsetof(struct TableProperties, comparator_name),
|
||||
OptionType::kEncodedString}},
|
||||
{"merge_operator_name",
|
||||
{offsetof(struct TableProperties, merge_operator_name),
|
||||
OptionType::kEncodedString}},
|
||||
{"prefix_extractor_name",
|
||||
{offsetof(struct TableProperties, prefix_extractor_name),
|
||||
OptionType::kEncodedString}},
|
||||
{"property_collectors_names",
|
||||
{offsetof(struct TableProperties, property_collectors_names),
|
||||
OptionType::kEncodedString}},
|
||||
{"compression_name",
|
||||
{offsetof(struct TableProperties, compression_name),
|
||||
OptionType::kEncodedString}},
|
||||
{"compression_options",
|
||||
{offsetof(struct TableProperties, compression_options),
|
||||
OptionType::kEncodedString}},
|
||||
{"seqno_to_time_mapping",
|
||||
{offsetof(struct TableProperties, seqno_to_time_mapping),
|
||||
OptionType::kEncodedString}},
|
||||
{"user_collected_properties",
|
||||
OptionTypeInfo::StringMap(
|
||||
offsetof(struct TableProperties, user_collected_properties),
|
||||
OptionVerificationType::kNormal, OptionTypeFlags::kNone)},
|
||||
{"readable_properties",
|
||||
OptionTypeInfo::StringMap(
|
||||
offsetof(struct TableProperties, readable_properties),
|
||||
OptionVerificationType::kNormal, OptionTypeFlags::kNone)},
|
||||
};
|
||||
|
||||
static std::unordered_map<std::string, OptionTypeInfo>
|
||||
@@ -467,6 +665,14 @@ static std::unordered_map<std::string, OptionTypeInfo>
|
||||
{offsetof(struct CompactionServiceOutputFile, epoch_number),
|
||||
OptionType::kUInt64T, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone}},
|
||||
{"file_checksum",
|
||||
{offsetof(struct CompactionServiceOutputFile, file_checksum),
|
||||
OptionType::kEncodedString, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone}},
|
||||
{"file_checksum_func_name",
|
||||
{offsetof(struct CompactionServiceOutputFile, file_checksum_func_name),
|
||||
OptionType::kEncodedString, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone}},
|
||||
{"paranoid_hash",
|
||||
{offsetof(struct CompactionServiceOutputFile, paranoid_hash),
|
||||
OptionType::kUInt64T, OptionVerificationType::kNormal,
|
||||
@@ -480,6 +686,11 @@ static std::unordered_map<std::string, OptionTypeInfo>
|
||||
offsetof(struct CompactionServiceOutputFile, unique_id),
|
||||
OptionVerificationType::kNormal, OptionTypeFlags::kNone,
|
||||
{0, OptionType::kUInt64T})},
|
||||
{"table_properties",
|
||||
OptionTypeInfo::Struct(
|
||||
"table_properties", &table_properties_type_info,
|
||||
offsetof(struct CompactionServiceOutputFile, table_properties),
|
||||
OptionVerificationType::kNormal, OptionTypeFlags::kNone)},
|
||||
};
|
||||
|
||||
static std::unordered_map<std::string, OptionTypeInfo>
|
||||
@@ -527,6 +738,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,
|
||||
@@ -695,14 +910,6 @@ static std::unordered_map<std::string, OptionTypeInfo> cs_result_type_info = {
|
||||
{offsetof(struct CompactionServiceResult, output_path),
|
||||
OptionType::kEncodedString, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone}},
|
||||
{"num_output_records",
|
||||
{offsetof(struct CompactionServiceResult, num_output_records),
|
||||
OptionType::kUInt64T, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone}},
|
||||
{"total_bytes",
|
||||
{offsetof(struct CompactionServiceResult, total_bytes),
|
||||
OptionType::kUInt64T, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone}},
|
||||
{"bytes_read",
|
||||
{offsetof(struct CompactionServiceResult, bytes_read),
|
||||
OptionType::kUInt64T, OptionVerificationType::kNormal,
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
|
||||
#include "db/db_test_util.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "rocksdb/utilities/options_util.h"
|
||||
#include "table/unique_id_impl.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
@@ -21,8 +21,10 @@ class MyTestCompactionService : public CompactionService {
|
||||
: db_path_(std::move(db_path)),
|
||||
options_(options),
|
||||
statistics_(statistics),
|
||||
start_info_("na", "na", "na", 0, Env::TOTAL),
|
||||
wait_info_("na", "na", "na", 0, Env::TOTAL),
|
||||
start_info_("na", "na", "na", 0, Env::TOTAL, CompactionReason::kUnknown,
|
||||
false, false, false),
|
||||
wait_info_("na", "na", "na", 0, Env::TOTAL, CompactionReason::kUnknown,
|
||||
false, false, false),
|
||||
listeners_(listeners),
|
||||
table_properties_collector_factories_(
|
||||
std::move(table_properties_collector_factories)) {}
|
||||
@@ -97,8 +99,12 @@ class MyTestCompactionService : public CompactionService {
|
||||
Status s =
|
||||
DB::OpenAndCompact(options, db_path_, db_path_ + "/" + scheduled_job_id,
|
||||
compaction_input, result, options_override);
|
||||
if (is_override_wait_result_) {
|
||||
*result = override_wait_result_;
|
||||
{
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
if (is_override_wait_result_) {
|
||||
*result = override_wait_result_;
|
||||
}
|
||||
result_ = *result;
|
||||
}
|
||||
compaction_num_.fetch_add(1);
|
||||
if (s.ok()) {
|
||||
@@ -141,6 +147,10 @@ class MyTestCompactionService : public CompactionService {
|
||||
|
||||
void SetCanceled(bool canceled) { canceled_ = canceled; }
|
||||
|
||||
void GetResult(CompactionServiceResult* deserialized) {
|
||||
CompactionServiceResult::Read(result_, deserialized).PermitUncheckedError();
|
||||
}
|
||||
|
||||
CompactionServiceJobStatus GetFinalCompactionServiceJobStatus() {
|
||||
return final_updated_status_.load();
|
||||
}
|
||||
@@ -162,6 +172,7 @@ class MyTestCompactionService : public CompactionService {
|
||||
CompactionServiceJobStatus override_wait_status_ =
|
||||
CompactionServiceJobStatus::kFailure;
|
||||
bool is_override_wait_result_ = false;
|
||||
std::string result_;
|
||||
std::string override_wait_result_;
|
||||
std::vector<std::shared_ptr<EventListener>> listeners_;
|
||||
std::vector<std::shared_ptr<TablePropertiesCollectorFactory>>
|
||||
@@ -331,6 +342,34 @@ TEST_F(CompactionServiceTest, BasicCompactions) {
|
||||
ReopenWithColumnFamilies({kDefaultColumnFamilyName, "cf_1", "cf_2", "cf_3"},
|
||||
options);
|
||||
ASSERT_GT(verify_passed, 0);
|
||||
CompactionServiceResult result;
|
||||
my_cs->GetResult(&result);
|
||||
if (s.IsAborted()) {
|
||||
ASSERT_NOK(result.status);
|
||||
} else {
|
||||
ASSERT_OK(result.status);
|
||||
}
|
||||
ASSERT_GE(result.stats.elapsed_micros, 1);
|
||||
ASSERT_GE(result.stats.cpu_micros, 1);
|
||||
|
||||
ASSERT_EQ(20, result.stats.num_output_records);
|
||||
ASSERT_EQ(result.output_files.size(), result.stats.num_output_files);
|
||||
|
||||
uint64_t total_size = 0;
|
||||
for (auto output_file : result.output_files) {
|
||||
std::string file_name = result.output_path + "/" + output_file.file_name;
|
||||
|
||||
uint64_t file_size = 0;
|
||||
ASSERT_OK(options.env->GetFileSize(file_name, &file_size));
|
||||
ASSERT_GT(file_size, 0);
|
||||
total_size += file_size;
|
||||
}
|
||||
ASSERT_EQ(total_size, result.stats.total_output_bytes);
|
||||
|
||||
ASSERT_TRUE(result.stats.is_remote_compaction);
|
||||
ASSERT_TRUE(result.stats.is_manual_compaction);
|
||||
ASSERT_FALSE(result.stats.is_full_compaction);
|
||||
|
||||
Close();
|
||||
}
|
||||
|
||||
@@ -369,6 +408,515 @@ TEST_F(CompactionServiceTest, ManualCompaction) {
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
ASSERT_GE(my_cs->GetCompactionNum(), comp_num + 1);
|
||||
VerifyTestData();
|
||||
|
||||
CompactionServiceResult result;
|
||||
my_cs->GetResult(&result);
|
||||
ASSERT_OK(result.status);
|
||||
ASSERT_TRUE(result.stats.is_manual_compaction);
|
||||
ASSERT_TRUE(result.stats.is_remote_compaction);
|
||||
}
|
||||
|
||||
TEST_F(CompactionServiceTest, PreservedOptionsLocalCompaction) {
|
||||
Options options = CurrentOptions();
|
||||
options.level0_file_num_compaction_trigger = 2;
|
||||
options.disable_auto_compactions = true;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
Random rnd(301);
|
||||
for (auto i = 0; i < 2; ++i) {
|
||||
for (auto j = 0; j < 10; ++j) {
|
||||
ASSERT_OK(
|
||||
Put("foo" + std::to_string(i * 10 + j), rnd.RandomString(1024)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"CompactionJob::ProcessKeyValueCompaction()::Processing", [&](void* arg) {
|
||||
auto compaction = static_cast<Compaction*>(arg);
|
||||
std::string options_file_name = OptionsFileName(
|
||||
dbname_,
|
||||
compaction->input_version()->version_set()->options_file_number());
|
||||
|
||||
// Change option twice to make sure the very first OPTIONS file gets
|
||||
// purged
|
||||
ASSERT_OK(dbfull()->SetOptions(
|
||||
{{"level0_file_num_compaction_trigger", "4"}}));
|
||||
ASSERT_EQ(4, dbfull()->GetOptions().level0_file_num_compaction_trigger);
|
||||
ASSERT_OK(dbfull()->SetOptions(
|
||||
{{"level0_file_num_compaction_trigger", "6"}}));
|
||||
ASSERT_EQ(6, dbfull()->GetOptions().level0_file_num_compaction_trigger);
|
||||
dbfull()->TEST_DeleteObsoleteFiles();
|
||||
|
||||
// For non-remote compactions, OPTIONS file can be deleted while
|
||||
// using option at the start of the compaction
|
||||
Status s = env_->FileExists(options_file_name);
|
||||
ASSERT_NOK(s);
|
||||
ASSERT_TRUE(s.IsNotFound());
|
||||
// Should be old value
|
||||
ASSERT_EQ(2, compaction->mutable_cf_options()
|
||||
->level0_file_num_compaction_trigger);
|
||||
ASSERT_TRUE(dbfull()->min_options_file_numbers_.empty());
|
||||
});
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
Status s = dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
||||
ASSERT_TRUE(s.ok());
|
||||
}
|
||||
|
||||
TEST_F(CompactionServiceTest, PreservedOptionsRemoteCompaction) {
|
||||
// For non-remote compaction do not preserve options file
|
||||
Options options = CurrentOptions();
|
||||
options.level0_file_num_compaction_trigger = 2;
|
||||
options.disable_auto_compactions = true;
|
||||
ReopenWithCompactionService(&options);
|
||||
GenerateTestData();
|
||||
|
||||
auto my_cs = GetCompactionService();
|
||||
|
||||
Random rnd(301);
|
||||
for (auto i = 0; i < 2; ++i) {
|
||||
for (auto j = 0; j < 10; ++j) {
|
||||
ASSERT_OK(
|
||||
Put("foo" + std::to_string(i * 10 + j), rnd.RandomString(1024)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"CompactionServiceTest::OptionsFileChanged",
|
||||
"DBImplSecondary::OpenAndCompact::BeforeLoadingOptions:1"}});
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImplSecondary::OpenAndCompact::BeforeLoadingOptions:0",
|
||||
[&](void* arg) {
|
||||
auto options_file_number = static_cast<uint64_t*>(arg);
|
||||
// Change the option twice before the compaction run
|
||||
ASSERT_OK(dbfull()->SetOptions(
|
||||
{{"level0_file_num_compaction_trigger", "4"}}));
|
||||
ASSERT_EQ(4, dbfull()->GetOptions().level0_file_num_compaction_trigger);
|
||||
ASSERT_TRUE(dbfull()->versions_->options_file_number() >
|
||||
*options_file_number);
|
||||
|
||||
// Change the option twice before the compaction run
|
||||
ASSERT_OK(dbfull()->SetOptions(
|
||||
{{"level0_file_num_compaction_trigger", "5"}}));
|
||||
ASSERT_EQ(5, dbfull()->GetOptions().level0_file_num_compaction_trigger);
|
||||
ASSERT_TRUE(dbfull()->versions_->options_file_number() >
|
||||
*options_file_number);
|
||||
|
||||
TEST_SYNC_POINT("CompactionServiceTest::OptionsFileChanged");
|
||||
});
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"CompactionServiceJob::ProcessKeyValueCompactionWithCompactionService",
|
||||
[&](void* arg) {
|
||||
auto input = static_cast<CompactionServiceInput*>(arg);
|
||||
std::string options_file_name =
|
||||
OptionsFileName(dbname_, input->options_file_number);
|
||||
|
||||
ASSERT_OK(env_->FileExists(options_file_name));
|
||||
ASSERT_FALSE(dbfull()->min_options_file_numbers_.empty());
|
||||
ASSERT_EQ(dbfull()->min_options_file_numbers_.front(),
|
||||
input->options_file_number);
|
||||
|
||||
DBOptions db_options;
|
||||
ConfigOptions config_options;
|
||||
std::vector<ColumnFamilyDescriptor> all_column_families;
|
||||
config_options.env = env_;
|
||||
ASSERT_OK(LoadOptionsFromFile(config_options, options_file_name,
|
||||
&db_options, &all_column_families));
|
||||
bool has_cf = false;
|
||||
for (auto& cf : all_column_families) {
|
||||
if (cf.name == input->cf_name) {
|
||||
// Should be old value
|
||||
ASSERT_EQ(2, cf.options.level0_file_num_compaction_trigger);
|
||||
has_cf = true;
|
||||
}
|
||||
}
|
||||
ASSERT_TRUE(has_cf);
|
||||
});
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"CompactionJob::ProcessKeyValueCompaction()::Processing", [&](void* arg) {
|
||||
auto compaction = static_cast<Compaction*>(arg);
|
||||
ASSERT_EQ(2, compaction->mutable_cf_options()
|
||||
->level0_file_num_compaction_trigger);
|
||||
});
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
Status s = dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
||||
ASSERT_TRUE(s.ok());
|
||||
|
||||
CompactionServiceResult result;
|
||||
my_cs->GetResult(&result);
|
||||
ASSERT_OK(result.status);
|
||||
ASSERT_TRUE(result.stats.is_manual_compaction);
|
||||
ASSERT_TRUE(result.stats.is_remote_compaction);
|
||||
}
|
||||
|
||||
class EventVerifier : public EventListener {
|
||||
public:
|
||||
explicit EventVerifier(uint64_t expected_num_input_records,
|
||||
size_t expected_num_input_files,
|
||||
uint64_t expected_num_output_records,
|
||||
size_t expected_num_output_files,
|
||||
const std::string& expected_smallest_output_key_prefix,
|
||||
const std::string& expected_largest_output_key_prefix,
|
||||
bool expected_is_remote_compaction_on_begin,
|
||||
bool expected_is_remote_compaction_on_complete)
|
||||
: expected_num_input_records_(expected_num_input_records),
|
||||
expected_num_input_files_(expected_num_input_files),
|
||||
expected_num_output_records_(expected_num_output_records),
|
||||
expected_num_output_files_(expected_num_output_files),
|
||||
expected_smallest_output_key_prefix_(
|
||||
expected_smallest_output_key_prefix),
|
||||
expected_largest_output_key_prefix_(expected_largest_output_key_prefix),
|
||||
expected_is_remote_compaction_on_begin_(
|
||||
expected_is_remote_compaction_on_begin),
|
||||
expected_is_remote_compaction_on_complete_(
|
||||
expected_is_remote_compaction_on_complete) {}
|
||||
void OnCompactionBegin(DB* /*db*/, const CompactionJobInfo& ci) override {
|
||||
ASSERT_EQ(expected_num_input_files_, ci.input_files.size());
|
||||
ASSERT_EQ(expected_num_input_files_, ci.input_file_infos.size());
|
||||
ASSERT_EQ(expected_is_remote_compaction_on_begin_,
|
||||
ci.stats.is_remote_compaction);
|
||||
ASSERT_TRUE(ci.stats.is_manual_compaction);
|
||||
ASSERT_FALSE(ci.stats.is_full_compaction);
|
||||
}
|
||||
void OnCompactionCompleted(DB* /*db*/, const CompactionJobInfo& ci) override {
|
||||
ASSERT_GT(ci.stats.elapsed_micros, 0);
|
||||
ASSERT_GT(ci.stats.cpu_micros, 0);
|
||||
ASSERT_EQ(expected_num_input_records_, ci.stats.num_input_records);
|
||||
ASSERT_EQ(expected_num_input_files_, ci.stats.num_input_files);
|
||||
ASSERT_EQ(expected_num_output_records_, ci.stats.num_output_records);
|
||||
ASSERT_EQ(expected_num_output_files_, ci.stats.num_output_files);
|
||||
ASSERT_EQ(expected_smallest_output_key_prefix_,
|
||||
ci.stats.smallest_output_key_prefix);
|
||||
ASSERT_EQ(expected_largest_output_key_prefix_,
|
||||
ci.stats.largest_output_key_prefix);
|
||||
ASSERT_GT(ci.stats.total_input_bytes, 0);
|
||||
ASSERT_GT(ci.stats.total_output_bytes, 0);
|
||||
ASSERT_EQ(ci.stats.num_input_records,
|
||||
ci.stats.num_output_records + ci.stats.num_records_replaced);
|
||||
ASSERT_EQ(expected_is_remote_compaction_on_complete_,
|
||||
ci.stats.is_remote_compaction);
|
||||
ASSERT_TRUE(ci.stats.is_manual_compaction);
|
||||
ASSERT_FALSE(ci.stats.is_full_compaction);
|
||||
}
|
||||
|
||||
private:
|
||||
uint64_t expected_num_input_records_;
|
||||
size_t expected_num_input_files_;
|
||||
uint64_t expected_num_output_records_;
|
||||
size_t expected_num_output_files_;
|
||||
std::string expected_smallest_output_key_prefix_;
|
||||
std::string expected_largest_output_key_prefix_;
|
||||
bool expected_is_remote_compaction_on_begin_;
|
||||
bool expected_is_remote_compaction_on_complete_;
|
||||
};
|
||||
|
||||
TEST_F(CompactionServiceTest, VerifyStats) {
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
auto event_verifier = std::make_shared<EventVerifier>(
|
||||
30 /* expected_num_input_records */, 3 /* expected_num_input_files */,
|
||||
20 /* expected_num_output_records */, 1 /* expected_num_output_files */,
|
||||
"key00000" /* expected_smallest_output_key_prefix */,
|
||||
"key00001" /* expected_largest_output_key_prefix */,
|
||||
true /* expected_is_remote_compaction_on_begin */,
|
||||
true /* expected_is_remote_compaction_on_complete */);
|
||||
options.listeners.push_back(event_verifier);
|
||||
ReopenWithCompactionService(&options);
|
||||
GenerateTestData();
|
||||
|
||||
auto my_cs = GetCompactionService();
|
||||
|
||||
std::string start_str = Key(0);
|
||||
std::string end_str = Key(1);
|
||||
Slice start(start_str);
|
||||
Slice end(end_str);
|
||||
uint64_t comp_num = my_cs->GetCompactionNum();
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), &start, &end));
|
||||
ASSERT_GE(my_cs->GetCompactionNum(), comp_num + 1);
|
||||
VerifyTestData();
|
||||
|
||||
CompactionServiceResult result;
|
||||
my_cs->GetResult(&result);
|
||||
ASSERT_OK(result.status);
|
||||
ASSERT_TRUE(result.stats.is_manual_compaction);
|
||||
ASSERT_TRUE(result.stats.is_remote_compaction);
|
||||
}
|
||||
|
||||
TEST_F(CompactionServiceTest, VerifyStatsLocalFallback) {
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
auto event_verifier = std::make_shared<EventVerifier>(
|
||||
30 /* expected_num_input_records */, 3 /* expected_num_input_files */,
|
||||
20 /* expected_num_output_records */, 1 /* expected_num_output_files */,
|
||||
"key00000" /* expected_smallest_output_key_prefix */,
|
||||
"key00001" /* expected_largest_output_key_prefix */,
|
||||
true /* expected_is_remote_compaction_on_begin */,
|
||||
false /* expected_is_remote_compaction_on_complete */);
|
||||
options.listeners.push_back(event_verifier);
|
||||
ReopenWithCompactionService(&options);
|
||||
GenerateTestData();
|
||||
|
||||
auto my_cs = GetCompactionService();
|
||||
my_cs->OverrideStartStatus(CompactionServiceJobStatus::kUseLocal);
|
||||
|
||||
std::string start_str = Key(0);
|
||||
std::string end_str = Key(1);
|
||||
Slice start(start_str);
|
||||
Slice end(end_str);
|
||||
uint64_t comp_num = my_cs->GetCompactionNum();
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), &start, &end));
|
||||
// Remote Compaction did not happen
|
||||
ASSERT_EQ(my_cs->GetCompactionNum(), comp_num);
|
||||
VerifyTestData();
|
||||
}
|
||||
|
||||
TEST_F(CompactionServiceTest, CorruptedOutput) {
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
ReopenWithCompactionService(&options);
|
||||
GenerateTestData();
|
||||
|
||||
auto my_cs = GetCompactionService();
|
||||
|
||||
std::string start_str = Key(15);
|
||||
std::string end_str = Key(45);
|
||||
Slice start(start_str);
|
||||
Slice end(end_str);
|
||||
uint64_t comp_num = my_cs->GetCompactionNum();
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"CompactionServiceCompactionJob::Run:0", [&](void* arg) {
|
||||
CompactionServiceResult* compaction_result =
|
||||
*(static_cast<CompactionServiceResult**>(arg));
|
||||
ASSERT_TRUE(compaction_result != nullptr &&
|
||||
!compaction_result->output_files.empty());
|
||||
// Corrupt files here
|
||||
for (const auto& output_file : compaction_result->output_files) {
|
||||
std::string file_name =
|
||||
compaction_result->output_path + "/" + output_file.file_name;
|
||||
|
||||
uint64_t file_size = 0;
|
||||
Status s = options.env->GetFileSize(file_name, &file_size);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_GT(file_size, 0);
|
||||
|
||||
ASSERT_OK(test::CorruptFile(env_, file_name, 0,
|
||||
static_cast<int>(file_size),
|
||||
true /* verifyChecksum */));
|
||||
}
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
// CompactRange() should fail
|
||||
Status s = db_->CompactRange(CompactRangeOptions(), &start, &end);
|
||||
ASSERT_NOK(s);
|
||||
ASSERT_TRUE(s.IsCorruption());
|
||||
|
||||
ASSERT_GE(my_cs->GetCompactionNum(), comp_num + 1);
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
|
||||
// On the worker side, the compaction is considered success
|
||||
// Verification is done on the primary side
|
||||
CompactionServiceResult result;
|
||||
my_cs->GetResult(&result);
|
||||
ASSERT_OK(result.status);
|
||||
ASSERT_TRUE(result.stats.is_manual_compaction);
|
||||
ASSERT_TRUE(result.stats.is_remote_compaction);
|
||||
}
|
||||
|
||||
TEST_F(CompactionServiceTest, CorruptedOutputParanoidFileCheck) {
|
||||
for (bool paranoid_file_check_enabled : {false, true}) {
|
||||
SCOPED_TRACE("paranoid_file_check_enabled=" +
|
||||
std::to_string(paranoid_file_check_enabled));
|
||||
|
||||
Options options = CurrentOptions();
|
||||
Destroy(options);
|
||||
options.disable_auto_compactions = true;
|
||||
options.paranoid_file_checks = paranoid_file_check_enabled;
|
||||
ReopenWithCompactionService(&options);
|
||||
GenerateTestData();
|
||||
|
||||
auto my_cs = GetCompactionService();
|
||||
|
||||
std::string start_str = Key(15);
|
||||
std::string end_str = Key(45);
|
||||
Slice start(start_str);
|
||||
Slice end(end_str);
|
||||
uint64_t comp_num = my_cs->GetCompactionNum();
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"CompactionServiceCompactionJob::Run:0", [&](void* arg) {
|
||||
CompactionServiceResult* compaction_result =
|
||||
*(static_cast<CompactionServiceResult**>(arg));
|
||||
ASSERT_TRUE(compaction_result != nullptr &&
|
||||
!compaction_result->output_files.empty());
|
||||
// Corrupt files here
|
||||
for (const auto& output_file : compaction_result->output_files) {
|
||||
std::string file_name =
|
||||
compaction_result->output_path + "/" + output_file.file_name;
|
||||
|
||||
// Corrupt very small range of bytes. This corruption is so small
|
||||
// that this isn't caught by default light-weight check
|
||||
ASSERT_OK(test::CorruptFile(env_, file_name, 0, 1,
|
||||
false /* verifyChecksum */));
|
||||
}
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
Status s = db_->CompactRange(CompactRangeOptions(), &start, &end);
|
||||
if (paranoid_file_check_enabled) {
|
||||
ASSERT_NOK(s);
|
||||
ASSERT_EQ(Status::Corruption("Paranoid checksums do not match"), s);
|
||||
} else {
|
||||
// CompactRange() goes through if paranoid file check is not enabled
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
|
||||
ASSERT_GE(my_cs->GetCompactionNum(), comp_num + 1);
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
|
||||
// On the worker side, the compaction is considered success
|
||||
// Verification is done on the primary side
|
||||
CompactionServiceResult result;
|
||||
my_cs->GetResult(&result);
|
||||
ASSERT_OK(result.status);
|
||||
ASSERT_TRUE(result.stats.is_manual_compaction);
|
||||
ASSERT_TRUE(result.stats.is_remote_compaction);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(CompactionServiceTest, TruncatedOutput) {
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
ReopenWithCompactionService(&options);
|
||||
GenerateTestData();
|
||||
|
||||
auto my_cs = GetCompactionService();
|
||||
|
||||
std::string start_str = Key(15);
|
||||
std::string end_str = Key(45);
|
||||
Slice start(start_str);
|
||||
Slice end(end_str);
|
||||
uint64_t comp_num = my_cs->GetCompactionNum();
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"CompactionServiceCompactionJob::Run:0", [&](void* arg) {
|
||||
CompactionServiceResult* compaction_result =
|
||||
*(static_cast<CompactionServiceResult**>(arg));
|
||||
ASSERT_TRUE(compaction_result != nullptr &&
|
||||
!compaction_result->output_files.empty());
|
||||
// Truncate files here
|
||||
for (const auto& output_file : compaction_result->output_files) {
|
||||
std::string file_name =
|
||||
compaction_result->output_path + "/" + output_file.file_name;
|
||||
|
||||
uint64_t file_size = 0;
|
||||
Status s = options.env->GetFileSize(file_name, &file_size);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_GT(file_size, 0);
|
||||
|
||||
ASSERT_OK(test::TruncateFile(env_, file_name, file_size / 2));
|
||||
}
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
// CompactRange() should fail
|
||||
Status s = db_->CompactRange(CompactRangeOptions(), &start, &end);
|
||||
ASSERT_NOK(s);
|
||||
ASSERT_TRUE(s.IsCorruption());
|
||||
|
||||
ASSERT_GE(my_cs->GetCompactionNum(), comp_num + 1);
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
|
||||
// On the worker side, the compaction is considered success
|
||||
// Verification is done on the primary side
|
||||
CompactionServiceResult result;
|
||||
my_cs->GetResult(&result);
|
||||
ASSERT_OK(result.status);
|
||||
ASSERT_TRUE(result.stats.is_manual_compaction);
|
||||
ASSERT_TRUE(result.stats.is_remote_compaction);
|
||||
}
|
||||
|
||||
TEST_F(CompactionServiceTest, CustomFileChecksum) {
|
||||
Options options = CurrentOptions();
|
||||
options.file_checksum_gen_factory = GetFileChecksumGenCrc32cFactory();
|
||||
ReopenWithCompactionService(&options);
|
||||
GenerateTestData();
|
||||
|
||||
auto my_cs = GetCompactionService();
|
||||
|
||||
std::string start_str = Key(15);
|
||||
std::string end_str = Key(45);
|
||||
Slice start(start_str);
|
||||
Slice end(end_str);
|
||||
uint64_t comp_num = my_cs->GetCompactionNum();
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"CompactionServiceCompactionJob::Run:0", [&](void* arg) {
|
||||
CompactionServiceResult* compaction_result =
|
||||
*(static_cast<CompactionServiceResult**>(arg));
|
||||
ASSERT_TRUE(compaction_result != nullptr &&
|
||||
!compaction_result->output_files.empty());
|
||||
// Validate Checksum files here
|
||||
for (const auto& output_file : compaction_result->output_files) {
|
||||
std::string file_name =
|
||||
compaction_result->output_path + "/" + output_file.file_name;
|
||||
|
||||
FileChecksumGenContext gen_context;
|
||||
gen_context.file_name = file_name;
|
||||
std::unique_ptr<FileChecksumGenerator> file_checksum_gen =
|
||||
options.file_checksum_gen_factory->CreateFileChecksumGenerator(
|
||||
gen_context);
|
||||
|
||||
std::unique_ptr<SequentialFile> file_reader;
|
||||
uint64_t file_size = 0;
|
||||
Status s = options.env->GetFileSize(file_name, &file_size);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_GT(file_size, 0);
|
||||
|
||||
s = options.env->NewSequentialFile(file_name, &file_reader,
|
||||
EnvOptions());
|
||||
ASSERT_OK(s);
|
||||
|
||||
Slice result;
|
||||
std::unique_ptr<char[]> scratch(new char[file_size]);
|
||||
s = file_reader->Read(file_size, &result, scratch.get());
|
||||
ASSERT_OK(s);
|
||||
|
||||
file_checksum_gen->Update(scratch.get(), result.size());
|
||||
file_checksum_gen->Finalize();
|
||||
|
||||
// Verify actual checksum and the func name
|
||||
ASSERT_EQ(file_checksum_gen->Name(),
|
||||
output_file.file_checksum_func_name);
|
||||
ASSERT_EQ(file_checksum_gen->GetChecksum(),
|
||||
output_file.file_checksum);
|
||||
}
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), &start, &end));
|
||||
ASSERT_GE(my_cs->GetCompactionNum(), comp_num + 1);
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
|
||||
CompactionServiceResult result;
|
||||
my_cs->GetResult(&result);
|
||||
ASSERT_OK(result.status);
|
||||
ASSERT_TRUE(result.stats.is_manual_compaction);
|
||||
ASSERT_TRUE(result.stats.is_remote_compaction);
|
||||
}
|
||||
|
||||
TEST_F(CompactionServiceTest, CancelCompactionOnRemoteSide) {
|
||||
@@ -601,11 +1149,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 +1185,71 @@ TEST_F(CompactionServiceTest, CompactionInfo) {
|
||||
}
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
info = my_cs->GetCompactionInfoForStart();
|
||||
ASSERT_EQ(CompactionReason::kLevelL0FilesNum, info.compaction_reason);
|
||||
ASSERT_EQ(false, info.is_manual_compaction);
|
||||
ASSERT_EQ(false, info.is_full_compaction);
|
||||
ASSERT_EQ(true, info.bottommost_level);
|
||||
ASSERT_EQ(Env::BOTTOM, info.priority);
|
||||
info = my_cs->GetCompactionInfoForWait();
|
||||
ASSERT_EQ(Env::BOTTOM, info.priority);
|
||||
ASSERT_EQ(CompactionReason::kLevelL0FilesNum, info.compaction_reason);
|
||||
ASSERT_EQ(false, info.is_manual_compaction);
|
||||
ASSERT_EQ(false, info.is_full_compaction);
|
||||
ASSERT_EQ(true, info.bottommost_level);
|
||||
|
||||
// Test Non-Bottommost Level
|
||||
options.num_levels = 4;
|
||||
ReopenWithCompactionService(&options);
|
||||
my_cs =
|
||||
static_cast_with_check<MyTestCompactionService>(GetCompactionService());
|
||||
|
||||
for (int i = 0; i < options.level0_file_num_compaction_trigger; i++) {
|
||||
for (int j = 0; j < 10; j++) {
|
||||
int key_id = i * 10 + j;
|
||||
ASSERT_OK(Put(Key(key_id), "value_new_new" + std::to_string(key_id)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
info = my_cs->GetCompactionInfoForStart();
|
||||
ASSERT_EQ(false, info.is_manual_compaction);
|
||||
ASSERT_EQ(false, info.is_full_compaction);
|
||||
ASSERT_EQ(false, info.bottommost_level);
|
||||
info = my_cs->GetCompactionInfoForWait();
|
||||
ASSERT_EQ(false, info.is_manual_compaction);
|
||||
ASSERT_EQ(false, info.is_full_compaction);
|
||||
ASSERT_EQ(false, info.bottommost_level);
|
||||
|
||||
// Test Full Compaction + Bottommost Level
|
||||
options.num_levels = 6;
|
||||
ReopenWithCompactionService(&options);
|
||||
my_cs =
|
||||
static_cast_with_check<MyTestCompactionService>(GetCompactionService());
|
||||
|
||||
for (int i = 0; i < 20; i++) {
|
||||
for (int j = 0; j < 10; j++) {
|
||||
int key_id = i * 10 + j;
|
||||
ASSERT_OK(Put(Key(key_id), "value_new_new" + std::to_string(key_id)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
|
||||
CompactRangeOptions cro;
|
||||
cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
|
||||
ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
|
||||
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
info = my_cs->GetCompactionInfoForStart();
|
||||
ASSERT_EQ(true, info.is_manual_compaction);
|
||||
ASSERT_EQ(true, info.is_full_compaction);
|
||||
ASSERT_EQ(true, info.bottommost_level);
|
||||
ASSERT_EQ(CompactionReason::kManualCompaction, info.compaction_reason);
|
||||
info = my_cs->GetCompactionInfoForWait();
|
||||
ASSERT_EQ(true, info.is_manual_compaction);
|
||||
ASSERT_EQ(true, info.is_full_compaction);
|
||||
ASSERT_EQ(true, info.bottommost_level);
|
||||
ASSERT_EQ(CompactionReason::kManualCompaction, info.compaction_reason);
|
||||
}
|
||||
|
||||
TEST_F(CompactionServiceTest, FallbackLocalAuto) {
|
||||
|
||||
@@ -39,7 +39,7 @@ void CompactionState::AggregateCompactionStats(
|
||||
InternalStats::CompactionStatsFull& compaction_stats,
|
||||
CompactionJobStats& compaction_job_stats) {
|
||||
for (const auto& sc : sub_compact_states) {
|
||||
sc.AggregateCompactionStats(compaction_stats);
|
||||
sc.AggregateCompactionOutputStats(compaction_stats);
|
||||
compaction_job_stats.Add(sc.compaction_job_stats);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
#include "rocksdb/sst_partitioner.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
void SubcompactionState::AggregateCompactionStats(
|
||||
void SubcompactionState::AggregateCompactionOutputStats(
|
||||
InternalStats::CompactionStatsFull& compaction_stats) const {
|
||||
compaction_stats.stats.Add(compaction_outputs_.stats_);
|
||||
if (HasPenultimateLevelOutputs()) {
|
||||
|
||||
@@ -179,7 +179,7 @@ class SubcompactionState {
|
||||
|
||||
void Cleanup(Cache* cache);
|
||||
|
||||
void AggregateCompactionStats(
|
||||
void AggregateCompactionOutputStats(
|
||||
InternalStats::CompactionStatsFull& compaction_stats) const;
|
||||
|
||||
CompactionOutputs& Current() const {
|
||||
|
||||
@@ -2512,6 +2512,7 @@ TEST_P(IteratorWriteTimeTest, ReadFromMemtables) {
|
||||
start_time + kSecondsPerRecording * (i + 1));
|
||||
}
|
||||
}
|
||||
ASSERT_EQ(kNumKeys, i);
|
||||
ASSERT_OK(iter->status());
|
||||
}
|
||||
|
||||
@@ -2531,12 +2532,13 @@ TEST_P(IteratorWriteTimeTest, ReadFromMemtables) {
|
||||
}
|
||||
}
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ(-1, i);
|
||||
}
|
||||
|
||||
// Reopen the DB and disable the seqno to time recording, data with user
|
||||
// specified write time can still get a write time before it's flushed.
|
||||
options.preserve_internal_time_seconds = 0;
|
||||
DestroyAndReopen(options);
|
||||
Reopen(options);
|
||||
ASSERT_OK(TimedPut(Key(kKeyWithWriteTime), rnd.RandomString(100),
|
||||
kUserSpecifiedWriteTime));
|
||||
{
|
||||
@@ -2613,6 +2615,7 @@ TEST_P(IteratorWriteTimeTest, ReadFromSstFile) {
|
||||
}
|
||||
}
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ(kNumKeys, i);
|
||||
}
|
||||
|
||||
// Backward iteration
|
||||
@@ -2632,12 +2635,13 @@ TEST_P(IteratorWriteTimeTest, ReadFromSstFile) {
|
||||
}
|
||||
}
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ(-1, i);
|
||||
}
|
||||
|
||||
// Reopen the DB and disable the seqno to time recording. Data retrieved from
|
||||
// SST files still have write time available.
|
||||
options.preserve_internal_time_seconds = 0;
|
||||
DestroyAndReopen(options);
|
||||
Reopen(options);
|
||||
|
||||
dbfull()->TEST_WaitForPeriodicTaskRun(
|
||||
[&] { mock_clock_->MockSleepForSeconds(kSecondsPerRecording); });
|
||||
@@ -2663,6 +2667,7 @@ TEST_P(IteratorWriteTimeTest, ReadFromSstFile) {
|
||||
start_time + kSecondsPerRecording * (i + 1));
|
||||
}
|
||||
}
|
||||
ASSERT_EQ(kNumKeys, i);
|
||||
ASSERT_OK(iter->status());
|
||||
}
|
||||
|
||||
@@ -2686,6 +2691,7 @@ TEST_P(IteratorWriteTimeTest, ReadFromSstFile) {
|
||||
VerifyKeyAndWriteTime(iter.get(), Key(i), 0);
|
||||
}
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ(kNumKeys, i);
|
||||
}
|
||||
Close();
|
||||
}
|
||||
|
||||
+1
-1
@@ -87,7 +87,7 @@ Status VerifySstFileChecksumInternal(const Options& options,
|
||||
options.block_protection_bytes_per_key, false /* skip_filters */,
|
||||
!kImmortal, false /* force_direct_prefetch */, -1 /* level */);
|
||||
reader_options.largest_seqno = largest_seqno;
|
||||
s = ioptions.table_factory->NewTableReader(
|
||||
s = options.table_factory->NewTableReader(
|
||||
read_options, reader_options, std::move(file_reader), file_size,
|
||||
&table_reader, false /* prefetch_index_and_filter_in_cache */);
|
||||
if (!s.ok()) {
|
||||
|
||||
+62
-27
@@ -563,7 +563,7 @@ TEST_P(DBBlockCacheTest1, WarmCacheWithBlocksDuringFlush) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBBlockCacheTest, DynamicallyWarmCacheDuringFlush) {
|
||||
TEST_F(DBBlockCacheTest, DynamicOptions) {
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
|
||||
@@ -578,39 +578,74 @@ TEST_F(DBBlockCacheTest, DynamicallyWarmCacheDuringFlush) {
|
||||
DestroyAndReopen(options);
|
||||
|
||||
std::string value(kValueSize, 'a');
|
||||
auto st = options.statistics;
|
||||
|
||||
for (size_t i = 1; i <= 5; i++) {
|
||||
ASSERT_OK(Put(std::to_string(i), value));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_EQ(1,
|
||||
options.statistics->getAndResetTickerCount(BLOCK_CACHE_DATA_ADD));
|
||||
size_t i = 1;
|
||||
ASSERT_OK(Put(std::to_string(i), value));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_EQ(1, st->getAndResetTickerCount(BLOCK_CACHE_DATA_ADD));
|
||||
|
||||
ASSERT_EQ(value, Get(std::to_string(i)));
|
||||
ASSERT_EQ(0,
|
||||
options.statistics->getAndResetTickerCount(BLOCK_CACHE_DATA_ADD));
|
||||
ASSERT_EQ(
|
||||
0, options.statistics->getAndResetTickerCount(BLOCK_CACHE_DATA_MISS));
|
||||
ASSERT_EQ(1,
|
||||
options.statistics->getAndResetTickerCount(BLOCK_CACHE_DATA_HIT));
|
||||
}
|
||||
ASSERT_EQ(value, Get(std::to_string(i)));
|
||||
ASSERT_EQ(0, st->getAndResetTickerCount(BLOCK_CACHE_DATA_ADD));
|
||||
ASSERT_EQ(0, st->getAndResetTickerCount(BLOCK_CACHE_DATA_MISS));
|
||||
ASSERT_EQ(1, st->getAndResetTickerCount(BLOCK_CACHE_DATA_HIT));
|
||||
|
||||
++i;
|
||||
ASSERT_OK(dbfull()->SetOptions(
|
||||
{{"block_based_table_factory", "{prepopulate_block_cache=kDisable;}"}}));
|
||||
|
||||
for (size_t i = 6; i <= kNumBlocks; i++) {
|
||||
ASSERT_OK(Put(std::to_string(i), value));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_EQ(0,
|
||||
options.statistics->getAndResetTickerCount(BLOCK_CACHE_DATA_ADD));
|
||||
ASSERT_OK(Put(std::to_string(i), value));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_EQ(0, st->getAndResetTickerCount(BLOCK_CACHE_DATA_ADD));
|
||||
|
||||
ASSERT_EQ(value, Get(std::to_string(i)));
|
||||
ASSERT_EQ(1,
|
||||
options.statistics->getAndResetTickerCount(BLOCK_CACHE_DATA_ADD));
|
||||
ASSERT_EQ(
|
||||
1, options.statistics->getAndResetTickerCount(BLOCK_CACHE_DATA_MISS));
|
||||
ASSERT_EQ(0,
|
||||
options.statistics->getAndResetTickerCount(BLOCK_CACHE_DATA_HIT));
|
||||
}
|
||||
ASSERT_EQ(value, Get(std::to_string(i)));
|
||||
ASSERT_EQ(1, st->getAndResetTickerCount(BLOCK_CACHE_DATA_ADD));
|
||||
ASSERT_EQ(1, st->getAndResetTickerCount(BLOCK_CACHE_DATA_MISS));
|
||||
ASSERT_EQ(0, st->getAndResetTickerCount(BLOCK_CACHE_DATA_HIT));
|
||||
|
||||
++i;
|
||||
ASSERT_OK(dbfull()->SetOptions({{"block_based_table_factory",
|
||||
"{prepopulate_block_cache=kFlushOnly;}"}}));
|
||||
|
||||
ASSERT_OK(Put(std::to_string(i), value));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_EQ(1, st->getAndResetTickerCount(BLOCK_CACHE_DATA_ADD));
|
||||
|
||||
ASSERT_EQ(value, Get(std::to_string(i)));
|
||||
ASSERT_EQ(0, st->getAndResetTickerCount(BLOCK_CACHE_DATA_ADD));
|
||||
ASSERT_EQ(0, st->getAndResetTickerCount(BLOCK_CACHE_DATA_MISS));
|
||||
ASSERT_EQ(1, st->getAndResetTickerCount(BLOCK_CACHE_DATA_HIT));
|
||||
|
||||
++i;
|
||||
// NOT YET SUPPORTED
|
||||
// FIXME: find a way to make this fail again (until well supported)
|
||||
// ASSERT_NOK(dbfull()->SetOptions(
|
||||
// {{"block_based_table_factory", "{block_cache=null;}"}}));
|
||||
|
||||
// ASSERT_OK(Put(std::to_string(i), value));
|
||||
// ASSERT_OK(Flush());
|
||||
// ASSERT_EQ(0, st->getAndResetTickerCount(BLOCK_CACHE_DATA_ADD));
|
||||
|
||||
// ASSERT_EQ(value, Get(std::to_string(i)));
|
||||
// ASSERT_EQ(0, st->getAndResetTickerCount(BLOCK_CACHE_DATA_ADD));
|
||||
// ASSERT_EQ(0, st->getAndResetTickerCount(BLOCK_CACHE_DATA_MISS));
|
||||
// ASSERT_EQ(0, st->getAndResetTickerCount(BLOCK_CACHE_DATA_HIT));
|
||||
|
||||
// ++i;
|
||||
|
||||
// NOT YET SUPPORTED
|
||||
// FIXME: find a way to make this fail again (until well supported)
|
||||
// ASSERT_NOK(dbfull()->SetOptions(
|
||||
// {{"block_based_table_factory", "{block_cache=1M;}"}}));
|
||||
|
||||
// ASSERT_OK(Put(std::to_string(i), value));
|
||||
// ASSERT_OK(Flush());
|
||||
// ASSERT_EQ(1, st->getAndResetTickerCount(BLOCK_CACHE_DATA_ADD));
|
||||
|
||||
// ASSERT_EQ(value, Get(std::to_string(i)));
|
||||
// ASSERT_EQ(0, st->getAndResetTickerCount(BLOCK_CACHE_DATA_ADD));
|
||||
// ASSERT_EQ(0, st->getAndResetTickerCount(BLOCK_CACHE_DATA_MISS));
|
||||
// ASSERT_EQ(1, st->getAndResetTickerCount(BLOCK_CACHE_DATA_HIT));
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -1975,10 +1975,24 @@ TEST_F(DBBloomFilterTest, MutatingRibbonFilterPolicy) {
|
||||
if (configs.empty()) {
|
||||
break;
|
||||
}
|
||||
std::string factory_field =
|
||||
(v[0] & 1) ? "table_factory" : "block_based_table_factory";
|
||||
|
||||
// Some irrelevant SetOptions to be sure they don't interfere
|
||||
ASSERT_OK(db_->SetOptions({{"level0_file_num_compaction_trigger", "10"}}));
|
||||
ASSERT_OK(
|
||||
db_->SetOptions({{"table_factory.filter_policy.bloom_before_level",
|
||||
db_->SetOptions({{"block_based_table_factory", "{block_size=1234}"}}));
|
||||
ASSERT_OK(db_->SetOptions({{factory_field + ".block_size", "12345"}}));
|
||||
|
||||
// Test the mutable field we're interested in
|
||||
ASSERT_OK(
|
||||
db_->SetOptions({{factory_field + ".filter_policy.bloom_before_level",
|
||||
configs.back().first}}));
|
||||
// FilterPolicy pointer should not have changed
|
||||
ASSERT_EQ(db_->GetOptions()
|
||||
.table_factory->GetOptions<BlockBasedTableOptions>()
|
||||
->filter_policy.get(),
|
||||
table_options.filter_policy.get());
|
||||
|
||||
// Ensure original object is mutated
|
||||
std::string val;
|
||||
@@ -1991,6 +2005,59 @@ TEST_F(DBBloomFilterTest, MutatingRibbonFilterPolicy) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBBloomFilterTest, MutableFilterPolicy) {
|
||||
// Test that BlockBasedTableOptions::filter_policy is mutable (replaceable)
|
||||
// with SetOptions.
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.statistics = CreateDBStatistics();
|
||||
auto& stats = *options.statistics;
|
||||
BlockBasedTableOptions table_options;
|
||||
// First config, to make sure there's no issues with this shared ptr
|
||||
// etc. when the DB switches filter policies.
|
||||
table_options.filter_policy.reset(NewBloomFilterPolicy(10));
|
||||
double expected_bpk = 10.0;
|
||||
// Other configs to try
|
||||
std::vector<std::pair<std::string, double>> configs = {
|
||||
{"ribbonfilter:10:-1", 7.0}, {"bloomfilter:5", 5.0}, {"nullptr", 0.0}};
|
||||
|
||||
table_options.cache_index_and_filter_blocks = true;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
options.level0_file_num_compaction_trigger =
|
||||
static_cast<int>(configs.size()) + 2;
|
||||
|
||||
ASSERT_OK(TryReopen(options));
|
||||
|
||||
char v[] = "a";
|
||||
|
||||
for (;; ++(v[0])) {
|
||||
const int maxKey = 8000;
|
||||
for (int i = 0; i < maxKey; i++) {
|
||||
ASSERT_OK(Put(Key(i), v));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
for (int i = 0; i < maxKey; i++) {
|
||||
ASSERT_EQ(Get(Key(i)), v);
|
||||
}
|
||||
|
||||
uint64_t filter_bytes =
|
||||
stats.getAndResetTickerCount(BLOCK_CACHE_FILTER_BYTES_INSERT);
|
||||
|
||||
EXPECT_NEAR(filter_bytes * 8.0 / maxKey, expected_bpk, 0.3);
|
||||
|
||||
if (configs.empty()) {
|
||||
break;
|
||||
}
|
||||
|
||||
ASSERT_OK(
|
||||
db_->SetOptions({{"block_based_table_factory",
|
||||
"{filter_policy=" + configs.back().first + "}"}}));
|
||||
expected_bpk = configs.back().second;
|
||||
configs.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
class SliceTransformLimitedDomain : public SliceTransform {
|
||||
const char* Name() const override { return "SliceTransformLimitedDomain"; }
|
||||
|
||||
|
||||
+80
-16
@@ -659,8 +659,9 @@ Status DBImpl::CloseHelper() {
|
||||
// We need to release them before the block cache is destroyed. The block
|
||||
// cache may be destroyed inside versions_.reset(), when column family data
|
||||
// list is destroyed, so leaving handles in table cache after
|
||||
// versions_.reset() may cause issues.
|
||||
// Here we clean all unreferenced handles in table cache.
|
||||
// versions_.reset() may cause issues. Here we clean all unreferenced handles
|
||||
// in table cache, and (for certain builds/conditions) assert that no obsolete
|
||||
// files are hanging around unreferenced (leak) in the table/blob file cache.
|
||||
// Now we assume all user queries have finished, so only version set itself
|
||||
// can possibly hold the blocks from block cache. After releasing unreferenced
|
||||
// handles here, only handles held by version set left and inside
|
||||
@@ -668,6 +669,9 @@ 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_) {
|
||||
@@ -1153,6 +1157,13 @@ void DBImpl::DumpStats() {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto* table_factory =
|
||||
cfd->GetCurrentMutableCFOptions()->table_factory.get();
|
||||
assert(table_factory != nullptr);
|
||||
// FIXME: need to a shared_ptr if/when block_cache is going to be mutable
|
||||
Cache* cache =
|
||||
table_factory->GetOptions<Cache>(TableFactory::kBlockCacheOpts());
|
||||
|
||||
// Release DB mutex for gathering cache entry stats. Pass over all
|
||||
// column families for this first so that other stats are dumped
|
||||
// near-atomically.
|
||||
@@ -1161,10 +1172,6 @@ void DBImpl::DumpStats() {
|
||||
|
||||
// Probe block cache for problems (if not already via another CF)
|
||||
if (immutable_db_options_.info_log) {
|
||||
auto* table_factory = cfd->ioptions()->table_factory.get();
|
||||
assert(table_factory != nullptr);
|
||||
Cache* cache =
|
||||
table_factory->GetOptions<Cache>(TableFactory::kBlockCacheOpts());
|
||||
if (cache && probed_caches.insert(cache).second) {
|
||||
cache->ReportProblems(immutable_db_options_.info_log);
|
||||
}
|
||||
@@ -3227,6 +3234,8 @@ Status DBImpl::MultiGetImpl(
|
||||
s = Status::Aborted();
|
||||
break;
|
||||
}
|
||||
// This could be a long-running operation
|
||||
ROCKSDB_THREAD_YIELD_HOOK();
|
||||
}
|
||||
|
||||
// Post processing (decrement reference counts and record statistics)
|
||||
@@ -3984,14 +3993,25 @@ std::unique_ptr<IterType> DBImpl::NewMultiCfIterator(
|
||||
"Different comparators are being used across CFs"));
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<Iterator*> child_iterators;
|
||||
Status s = NewIterators(_read_options, column_families, &child_iterators);
|
||||
if (!s.ok()) {
|
||||
return error_iterator_func(s);
|
||||
}
|
||||
return std::make_unique<ImplType>(column_families[0]->GetComparator(),
|
||||
column_families,
|
||||
std::move(child_iterators));
|
||||
|
||||
assert(column_families.size() == child_iterators.size());
|
||||
|
||||
std::vector<std::pair<ColumnFamilyHandle*, std::unique_ptr<Iterator>>>
|
||||
cfh_iter_pairs;
|
||||
cfh_iter_pairs.reserve(column_families.size());
|
||||
for (size_t i = 0; i < column_families.size(); ++i) {
|
||||
cfh_iter_pairs.emplace_back(column_families[i], child_iterators[i]);
|
||||
}
|
||||
|
||||
return std::make_unique<ImplType>(_read_options,
|
||||
column_families[0]->GetComparator(),
|
||||
std::move(cfh_iter_pairs));
|
||||
}
|
||||
|
||||
Status DBImpl::NewIterators(
|
||||
@@ -4295,8 +4315,8 @@ void DBImpl::ReleaseSnapshot(const Snapshot* s) {
|
||||
}
|
||||
// Avoid to go through every column family by checking a global threshold
|
||||
// first.
|
||||
CfdList cf_scheduled;
|
||||
if (oldest_snapshot > bottommost_files_mark_threshold_) {
|
||||
CfdList cf_scheduled;
|
||||
for (auto* cfd : *versions_->GetColumnFamilySet()) {
|
||||
if (!cfd->ioptions()->allow_ingest_behind) {
|
||||
cfd->current()->storage_info()->UpdateOldestSnapshot(
|
||||
@@ -4328,6 +4348,24 @@ void DBImpl::ReleaseSnapshot(const Snapshot* s) {
|
||||
}
|
||||
bottommost_files_mark_threshold_ = new_bottommost_files_mark_threshold;
|
||||
}
|
||||
|
||||
// Avoid to go through every column family by checking a global threshold
|
||||
// first.
|
||||
if (oldest_snapshot >= standalone_range_deletion_files_mark_threshold_) {
|
||||
for (auto* cfd : *versions_->GetColumnFamilySet()) {
|
||||
if (cfd->IsDropped() || CfdListContains(cf_scheduled, cfd)) {
|
||||
continue;
|
||||
}
|
||||
if (oldest_snapshot >=
|
||||
cfd->current()
|
||||
->storage_info()
|
||||
->standalone_range_tombstone_files_mark_threshold()) {
|
||||
EnqueuePendingCompaction(cfd);
|
||||
MaybeScheduleFlushOrCompaction();
|
||||
cf_scheduled.push_back(cfd);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
delete casted_s;
|
||||
}
|
||||
@@ -4779,6 +4817,24 @@ void DBImpl::ReleaseFileNumberFromPendingOutputs(
|
||||
}
|
||||
}
|
||||
|
||||
std::list<uint64_t>::iterator DBImpl::CaptureOptionsFileNumber() {
|
||||
// We need to remember the iterator of our insert, because after the
|
||||
// compaction is done, we need to remove that element from
|
||||
// min_options_file_numbers_.
|
||||
min_options_file_numbers_.push_back(versions_->options_file_number());
|
||||
auto min_options_file_numbers_inserted_elem = min_options_file_numbers_.end();
|
||||
--min_options_file_numbers_inserted_elem;
|
||||
return min_options_file_numbers_inserted_elem;
|
||||
}
|
||||
|
||||
void DBImpl::ReleaseOptionsFileNumber(
|
||||
std::unique_ptr<std::list<uint64_t>::iterator>& v) {
|
||||
if (v.get() != nullptr) {
|
||||
min_options_file_numbers_.erase(*v.get());
|
||||
v.reset();
|
||||
}
|
||||
}
|
||||
|
||||
Status DBImpl::GetUpdatesSince(
|
||||
SequenceNumber seq, std::unique_ptr<TransactionLogIterator>* iter,
|
||||
const TransactionLogIterator::ReadOptions& read_options) {
|
||||
@@ -5142,11 +5198,12 @@ Status DBImpl::GetDbIdentity(std::string& identity) const {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status DBImpl::GetDbIdentityFromIdentityFile(std::string* identity) const {
|
||||
Status DBImpl::GetDbIdentityFromIdentityFile(const IOOptions& opts,
|
||||
std::string* identity) const {
|
||||
std::string idfilename = IdentityFileName(dbname_);
|
||||
const FileOptions soptions;
|
||||
|
||||
Status s = ReadFileToString(fs_.get(), idfilename, identity);
|
||||
Status s = ReadFileToString(fs_.get(), idfilename, opts, identity);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -5457,7 +5514,8 @@ Status DBImpl::WriteOptionsFile(const WriteOptions& write_options,
|
||||
file_name, fs_.get());
|
||||
|
||||
if (s.ok()) {
|
||||
s = RenameTempFileToOptionsFile(file_name);
|
||||
s = RenameTempFileToOptionsFile(file_name,
|
||||
db_options.compaction_service != nullptr);
|
||||
}
|
||||
|
||||
if (!s.ok() && GetEnv()->FileExists(file_name).ok()) {
|
||||
@@ -5534,7 +5592,8 @@ Status DBImpl::DeleteObsoleteOptionsFiles() {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status DBImpl::RenameTempFileToOptionsFile(const std::string& file_name) {
|
||||
Status DBImpl::RenameTempFileToOptionsFile(const std::string& file_name,
|
||||
bool is_remote_compaction_enabled) {
|
||||
Status s;
|
||||
|
||||
uint64_t options_file_number = versions_->NewFileNumber();
|
||||
@@ -5578,7 +5637,7 @@ Status DBImpl::RenameTempFileToOptionsFile(const std::string& file_name) {
|
||||
my_disable_delete_obsolete_files = disable_delete_obsolete_files_;
|
||||
}
|
||||
|
||||
if (!my_disable_delete_obsolete_files) {
|
||||
if (!my_disable_delete_obsolete_files && !is_remote_compaction_enabled) {
|
||||
// TODO: Should we check for errors here?
|
||||
DeleteObsoleteOptionsFiles().PermitUncheckedError();
|
||||
}
|
||||
@@ -5811,7 +5870,6 @@ Status DBImpl::IngestExternalFile(
|
||||
Status DBImpl::IngestExternalFiles(
|
||||
const std::vector<IngestExternalFileArg>& args) {
|
||||
// TODO: plumb Env::IOActivity, Env::IOPriority
|
||||
const ReadOptions read_options;
|
||||
const WriteOptions write_options;
|
||||
|
||||
if (args.empty()) {
|
||||
@@ -5837,6 +5895,10 @@ Status DBImpl::IngestExternalFiles(
|
||||
snprintf(err_msg, 128, "external_files[%zu] is empty", i);
|
||||
return Status::InvalidArgument(err_msg);
|
||||
}
|
||||
if (i && args[i].options.fill_cache != args[i - 1].options.fill_cache) {
|
||||
return Status::InvalidArgument(
|
||||
"fill_cache should be the same across ingestion options.");
|
||||
}
|
||||
}
|
||||
for (const auto& arg : args) {
|
||||
const IngestExternalFileOptions& ingest_opts = arg.options;
|
||||
@@ -6024,6 +6086,8 @@ Status DBImpl::IngestExternalFiles(
|
||||
}
|
||||
}
|
||||
if (status.ok()) {
|
||||
ReadOptions read_options;
|
||||
read_options.fill_cache = args[0].options.fill_cache;
|
||||
autovector<ColumnFamilyData*> cfds_to_commit;
|
||||
autovector<const MutableCFOptions*> mutable_cf_options_list;
|
||||
autovector<autovector<VersionEdit*>> edit_lists;
|
||||
|
||||
+38
-12
@@ -482,7 +482,8 @@ class DBImpl : public DB {
|
||||
|
||||
Status GetDbIdentity(std::string& identity) const override;
|
||||
|
||||
virtual Status GetDbIdentityFromIdentityFile(std::string* identity) const;
|
||||
virtual Status GetDbIdentityFromIdentityFile(const IOOptions& opts,
|
||||
std::string* identity) const;
|
||||
|
||||
Status GetDbSessionId(std::string& session_id) const override;
|
||||
|
||||
@@ -853,6 +854,8 @@ class DBImpl : public DB {
|
||||
|
||||
uint64_t GetObsoleteSstFilesSize();
|
||||
|
||||
uint64_t MinOptionsFileNumberToKeep();
|
||||
|
||||
// Returns the list of live files in 'live' and the list
|
||||
// of all files in the filesystem in 'candidate_files'.
|
||||
// If force == false and the last call was less than
|
||||
@@ -1197,9 +1200,7 @@ class DBImpl : public DB {
|
||||
|
||||
uint64_t TEST_total_log_size() const { return total_log_size_; }
|
||||
|
||||
// Returns column family name to ImmutableCFOptions map.
|
||||
Status TEST_GetAllImmutableCFOptions(
|
||||
std::unordered_map<std::string, const ImmutableCFOptions*>* iopts_map);
|
||||
void TEST_GetAllBlockCaches(std::unordered_set<const Cache*>* cache_set);
|
||||
|
||||
// Return the lastest MutableCFOptions of a column family
|
||||
Status TEST_GetLatestMutableCFOptions(ColumnFamilyHandle* column_family,
|
||||
@@ -1239,9 +1240,14 @@ class DBImpl : public DB {
|
||||
static Status TEST_ValidateOptions(const DBOptions& db_options) {
|
||||
return ValidateOptions(db_options);
|
||||
}
|
||||
|
||||
#endif // NDEBUG
|
||||
|
||||
// In certain configurations, verify that the table/blob file cache only
|
||||
// contains entries for live files, to check for effective leaks of open
|
||||
// files. This can only be called when purging of obsolete files has
|
||||
// "settled," such as during parts of DB Close().
|
||||
void TEST_VerifyNoObsoleteFilesCached(bool db_mutex_already_held) const;
|
||||
|
||||
// persist stats to column family "_persistent_stats"
|
||||
void PersistStats();
|
||||
|
||||
@@ -1463,7 +1469,8 @@ class DBImpl : public DB {
|
||||
// The following two functions can only be called when:
|
||||
// 1. WriteThread::Writer::EnterUnbatched() is used.
|
||||
// 2. db_mutex is NOT held
|
||||
Status RenameTempFileToOptionsFile(const std::string& file_name);
|
||||
Status RenameTempFileToOptionsFile(const std::string& file_name,
|
||||
bool is_remote_compaction_enabled);
|
||||
Status DeleteObsoleteOptionsFiles();
|
||||
|
||||
void NotifyOnManualFlushScheduled(autovector<ColumnFamilyData*> cfds,
|
||||
@@ -1587,7 +1594,7 @@ class DBImpl : public DB {
|
||||
// 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,
|
||||
bool is_new_db, VersionEdit* version_edit);
|
||||
bool is_new_db, bool is_retry, 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);
|
||||
|
||||
@@ -1694,6 +1701,8 @@ class DBImpl : public DB {
|
||||
friend class XFTransactionWriteHandler;
|
||||
friend class DBBlobIndexTest;
|
||||
friend class WriteUnpreparedTransactionTest_RecoveryTest_Test;
|
||||
friend class CompactionServiceTest_PreservedOptionsLocalCompaction_Test;
|
||||
friend class CompactionServiceTest_PreservedOptionsRemoteCompaction_Test;
|
||||
#endif
|
||||
|
||||
struct CompactionState;
|
||||
@@ -1965,6 +1974,12 @@ class DBImpl : public DB {
|
||||
void ReleaseFileNumberFromPendingOutputs(
|
||||
std::unique_ptr<std::list<uint64_t>::iterator>& v);
|
||||
|
||||
// Similar to pending_outputs, preserve OPTIONS file. Used for remote
|
||||
// compaction.
|
||||
std::list<uint64_t>::iterator CaptureOptionsFileNumber();
|
||||
void ReleaseOptionsFileNumber(
|
||||
std::unique_ptr<std::list<uint64_t>::iterator>& v);
|
||||
|
||||
// Sets bg error if there is an error writing to WAL.
|
||||
IOStatus SyncClosedWals(const WriteOptions& write_options,
|
||||
JobContext* job_context, VersionEdit* synced_wals,
|
||||
@@ -2076,17 +2091,18 @@ class DBImpl : public DB {
|
||||
// memtable pending flush.
|
||||
// resuming_from_bg_err indicates whether the caller is attempting to resume
|
||||
// from background error.
|
||||
Status WaitForFlushMemTable(ColumnFamilyData* cfd,
|
||||
const uint64_t* flush_memtable_id = nullptr,
|
||||
bool resuming_from_bg_err = false) {
|
||||
Status WaitForFlushMemTable(
|
||||
ColumnFamilyData* cfd, const uint64_t* flush_memtable_id = nullptr,
|
||||
bool resuming_from_bg_err = false,
|
||||
std::optional<FlushReason> flush_reason = std::nullopt) {
|
||||
return WaitForFlushMemTables({cfd}, {flush_memtable_id},
|
||||
resuming_from_bg_err);
|
||||
resuming_from_bg_err, flush_reason);
|
||||
}
|
||||
// Wait for memtables to be flushed for multiple column families.
|
||||
Status WaitForFlushMemTables(
|
||||
const autovector<ColumnFamilyData*>& cfds,
|
||||
const autovector<const uint64_t*>& flush_memtable_ids,
|
||||
bool resuming_from_bg_err);
|
||||
bool resuming_from_bg_err, std::optional<FlushReason> flush_reason);
|
||||
|
||||
inline void WaitForPendingWrites() {
|
||||
mutex_.AssertHeld();
|
||||
@@ -2755,6 +2771,11 @@ class DBImpl : public DB {
|
||||
// State is protected with db mutex.
|
||||
std::list<uint64_t> pending_outputs_;
|
||||
|
||||
// Similar to pending_outputs_, FindObsoleteFiles()/PurgeObsoleteFiles() never
|
||||
// deletes any OPTIONS file that has number bigger than any of the file number
|
||||
// in min_options_file_numbers_.
|
||||
std::list<uint64_t> min_options_file_numbers_;
|
||||
|
||||
// flush_queue_ and compaction_queue_ hold column families that we need to
|
||||
// flush and compact, respectively.
|
||||
// A column family is inserted into flush_queue_ when it satisfies condition
|
||||
@@ -2877,6 +2898,11 @@ class DBImpl : public DB {
|
||||
// garbages, among all column families.
|
||||
SequenceNumber bottommost_files_mark_threshold_ = kMaxSequenceNumber;
|
||||
|
||||
// The min threshold to trigger compactions for standalone range deletion
|
||||
// files that are marked for compaction.
|
||||
SequenceNumber standalone_range_deletion_files_mark_threshold_ =
|
||||
kMaxSequenceNumber;
|
||||
|
||||
LogsWithPrepTracker logs_with_prep_tracker_;
|
||||
|
||||
// Callback for compaction to check if a key is visible to a snapshot.
|
||||
|
||||
@@ -1561,6 +1561,12 @@ Status DBImpl::CompactFilesImpl(
|
||||
|
||||
compaction_job.Prepare();
|
||||
|
||||
std::unique_ptr<std::list<uint64_t>::iterator> min_options_file_number_elem;
|
||||
if (immutable_db_options().compaction_service != nullptr) {
|
||||
min_options_file_number_elem.reset(
|
||||
new std::list<uint64_t>::iterator(CaptureOptionsFileNumber()));
|
||||
}
|
||||
|
||||
mutex_.Unlock();
|
||||
TEST_SYNC_POINT("CompactFilesImpl:0");
|
||||
TEST_SYNC_POINT("CompactFilesImpl:1");
|
||||
@@ -1570,6 +1576,10 @@ Status DBImpl::CompactFilesImpl(
|
||||
TEST_SYNC_POINT("CompactFilesImpl:3");
|
||||
mutex_.Lock();
|
||||
|
||||
if (immutable_db_options().compaction_service != nullptr) {
|
||||
ReleaseOptionsFileNumber(min_options_file_number_elem);
|
||||
}
|
||||
|
||||
bool compaction_released = false;
|
||||
Status status =
|
||||
compaction_job.Install(*c->mutable_cf_options(), &compaction_released);
|
||||
@@ -1852,8 +1862,9 @@ Status DBImpl::ReFitLevel(ColumnFamilyData* cfd, int level, int target_level) {
|
||||
mutable_cf_options.compression_opts,
|
||||
mutable_cf_options.default_write_temperature,
|
||||
0 /* max_subcompactions, not applicable */,
|
||||
{} /* grandparents, not applicable */, false /* is manual */,
|
||||
"" /* trim_ts */, -1 /* score, not applicable */,
|
||||
{} /* grandparents, not applicable */,
|
||||
std::nullopt /* earliest_snapshot */, nullptr /* snapshot_checker */,
|
||||
false /* is manual */, "" /* trim_ts */, -1 /* score, not applicable */,
|
||||
false /* is deletion compaction, not applicable */,
|
||||
false /* l0_files_might_overlap, not applicable */,
|
||||
CompactionReason::kRefitLevel));
|
||||
@@ -2407,7 +2418,8 @@ Status DBImpl::FlushMemTable(ColumnFamilyData* cfd,
|
||||
}
|
||||
s = WaitForFlushMemTables(
|
||||
cfds, flush_memtable_ids,
|
||||
flush_reason == FlushReason::kErrorRecovery /* resuming_from_bg_err */);
|
||||
flush_reason == FlushReason::kErrorRecovery /* resuming_from_bg_err */,
|
||||
flush_reason);
|
||||
InstrumentedMutexLock lock_guard(&mutex_);
|
||||
for (auto* tmp_cfd : cfds) {
|
||||
tmp_cfd->UnrefAndTryDelete();
|
||||
@@ -2549,7 +2561,8 @@ Status DBImpl::AtomicFlushMemTables(
|
||||
}
|
||||
s = WaitForFlushMemTables(
|
||||
cfds, flush_memtable_ids,
|
||||
flush_reason == FlushReason::kErrorRecovery /* resuming_from_bg_err */);
|
||||
flush_reason == FlushReason::kErrorRecovery /* resuming_from_bg_err */,
|
||||
flush_reason);
|
||||
InstrumentedMutexLock lock_guard(&mutex_);
|
||||
for (auto* cfd : cfds) {
|
||||
cfd->UnrefAndTryDelete();
|
||||
@@ -2612,7 +2625,7 @@ Status DBImpl::RetryFlushesForErrorRecovery(FlushReason flush_reason,
|
||||
flush_memtable_id_ptrs.push_back(&flush_memtable_id);
|
||||
}
|
||||
s = WaitForFlushMemTables(cfds, flush_memtable_id_ptrs,
|
||||
true /* resuming_from_bg_err */);
|
||||
true /* resuming_from_bg_err */, flush_reason);
|
||||
mutex_.Lock();
|
||||
}
|
||||
|
||||
@@ -2712,7 +2725,7 @@ Status DBImpl::WaitUntilFlushWouldNotStallWrites(ColumnFamilyData* cfd,
|
||||
Status DBImpl::WaitForFlushMemTables(
|
||||
const autovector<ColumnFamilyData*>& cfds,
|
||||
const autovector<const uint64_t*>& flush_memtable_ids,
|
||||
bool resuming_from_bg_err) {
|
||||
bool resuming_from_bg_err, std::optional<FlushReason> flush_reason) {
|
||||
int num = static_cast<int>(cfds.size());
|
||||
// Wait until the compaction completes
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
@@ -2750,7 +2763,15 @@ Status DBImpl::WaitForFlushMemTables(
|
||||
(flush_memtable_ids[i] != nullptr &&
|
||||
cfds[i]->imm()->GetEarliestMemTableID() >
|
||||
*flush_memtable_ids[i])) {
|
||||
++num_finished;
|
||||
// Make file ingestion's flush wait until SuperVersion is also updated
|
||||
// since after flush, it does range overlapping check and file level
|
||||
// assignment with the current SuperVersion.
|
||||
if (!flush_reason.has_value() ||
|
||||
flush_reason.value() != FlushReason::kExternalFileIngestion ||
|
||||
cfds[i]->GetSuperVersion()->imm->GetID() ==
|
||||
cfds[i]->imm()->current()->GetID()) {
|
||||
++num_finished;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (1 == num_dropped && 1 == num) {
|
||||
@@ -3541,6 +3562,14 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
is_manual && manual_compaction->disallow_trivial_move;
|
||||
|
||||
CompactionJobStats compaction_job_stats;
|
||||
// Set is_remote_compaction to true on CompactionBegin Event if
|
||||
// compaction_service is set except for trivial moves. We do not know whether
|
||||
// remote compaction will actually be successfully scheduled, or fall back to
|
||||
// local at this time. CompactionCompleted event will tell the truth where
|
||||
// the compaction actually happened.
|
||||
compaction_job_stats.is_remote_compaction =
|
||||
immutable_db_options().compaction_service != nullptr;
|
||||
|
||||
Status status;
|
||||
if (!error_handler_.IsBGWorkStopped()) {
|
||||
if (shutting_down_.load(std::memory_order_acquire)) {
|
||||
@@ -3661,8 +3690,20 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
// compaction is not necessary. Need to make sure mutex is held
|
||||
// until we make a copy in the following code
|
||||
TEST_SYNC_POINT("DBImpl::BackgroundCompaction():BeforePickCompaction");
|
||||
SnapshotChecker* snapshot_checker = nullptr;
|
||||
std::vector<SequenceNumber> snapshot_seqs;
|
||||
// This info is not useful for other scenarios, so save querying existing
|
||||
// snapshots for those cases.
|
||||
if (cfd->ioptions()->compaction_style == kCompactionStyleUniversal &&
|
||||
cfd->user_comparator()->timestamp_size() == 0) {
|
||||
SequenceNumber earliest_write_conflict_snapshot;
|
||||
GetSnapshotContext(job_context, &snapshot_seqs,
|
||||
&earliest_write_conflict_snapshot,
|
||||
&snapshot_checker);
|
||||
assert(is_snapshot_supported_ || snapshots_.empty());
|
||||
}
|
||||
c.reset(cfd->PickCompaction(*mutable_cf_options, mutable_db_options_,
|
||||
log_buffer));
|
||||
snapshot_seqs, snapshot_checker, log_buffer));
|
||||
TEST_SYNC_POINT("DBImpl::BackgroundCompaction():AfterPickCompaction");
|
||||
|
||||
if (c != nullptr) {
|
||||
@@ -3766,6 +3807,8 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
ThreadStatusUtil::SetThreadOperation(ThreadStatus::OP_COMPACTION);
|
||||
|
||||
compaction_job_stats.num_input_files = c->num_input_files(0);
|
||||
// Trivial moves do not get compacted remotely
|
||||
compaction_job_stats.is_remote_compaction = false;
|
||||
|
||||
NotifyOnCompactionBegin(c->column_family_data(), c.get(), status,
|
||||
compaction_job_stats, job_context->job_id);
|
||||
@@ -3901,6 +3944,12 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
&bg_bottom_compaction_scheduled_);
|
||||
compaction_job.Prepare();
|
||||
|
||||
std::unique_ptr<std::list<uint64_t>::iterator> min_options_file_number_elem;
|
||||
if (immutable_db_options().compaction_service != nullptr) {
|
||||
min_options_file_number_elem.reset(
|
||||
new std::list<uint64_t>::iterator(CaptureOptionsFileNumber()));
|
||||
}
|
||||
|
||||
NotifyOnCompactionBegin(c->column_family_data(), c.get(), status,
|
||||
compaction_job_stats, job_context->job_id);
|
||||
mutex_.Unlock();
|
||||
@@ -3910,6 +3959,11 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
compaction_job.Run().PermitUncheckedError();
|
||||
TEST_SYNC_POINT("DBImpl::BackgroundCompaction:NonTrivial:AfterRun");
|
||||
mutex_.Lock();
|
||||
|
||||
if (immutable_db_options().compaction_service != nullptr) {
|
||||
ReleaseOptionsFileNumber(min_options_file_number_elem);
|
||||
}
|
||||
|
||||
status =
|
||||
compaction_job.Install(*c->mutable_cf_options(), &compaction_released);
|
||||
io_s = compaction_job.io_status();
|
||||
@@ -4256,12 +4310,18 @@ void DBImpl::InstallSuperVersionAndScheduleWork(
|
||||
// newer snapshot created and released frequently, the compaction will be
|
||||
// triggered soon anyway.
|
||||
bottommost_files_mark_threshold_ = kMaxSequenceNumber;
|
||||
standalone_range_deletion_files_mark_threshold_ = kMaxSequenceNumber;
|
||||
for (auto* my_cfd : *versions_->GetColumnFamilySet()) {
|
||||
if (!my_cfd->ioptions()->allow_ingest_behind) {
|
||||
bottommost_files_mark_threshold_ = std::min(
|
||||
bottommost_files_mark_threshold_,
|
||||
my_cfd->current()->storage_info()->bottommost_files_mark_threshold());
|
||||
}
|
||||
standalone_range_deletion_files_mark_threshold_ =
|
||||
std::min(standalone_range_deletion_files_mark_threshold_,
|
||||
cfd->current()
|
||||
->storage_info()
|
||||
->standalone_range_tombstone_files_mark_threshold());
|
||||
}
|
||||
|
||||
// Whenever we install new SuperVersion, we might need to issue new flushes or
|
||||
|
||||
+53
-15
@@ -9,6 +9,7 @@
|
||||
|
||||
#ifndef NDEBUG
|
||||
|
||||
#include "db/blob/blob_file_cache.h"
|
||||
#include "db/column_family.h"
|
||||
#include "db/db_impl/db_impl.h"
|
||||
#include "db/error_handler.h"
|
||||
@@ -232,23 +233,16 @@ uint64_t DBImpl::TEST_LogfileNumber() {
|
||||
return logfile_number_;
|
||||
}
|
||||
|
||||
Status DBImpl::TEST_GetAllImmutableCFOptions(
|
||||
std::unordered_map<std::string, const ImmutableCFOptions*>* iopts_map) {
|
||||
std::vector<std::string> cf_names;
|
||||
std::vector<const ImmutableCFOptions*> iopts;
|
||||
{
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
for (auto cfd : *versions_->GetColumnFamilySet()) {
|
||||
cf_names.push_back(cfd->GetName());
|
||||
iopts.push_back(cfd->ioptions());
|
||||
void DBImpl::TEST_GetAllBlockCaches(
|
||||
std::unordered_set<const Cache*>* cache_set) {
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
for (auto cfd : *versions_->GetColumnFamilySet()) {
|
||||
if (const auto bbto =
|
||||
cfd->GetCurrentMutableCFOptions()
|
||||
->table_factory->GetOptions<BlockBasedTableOptions>()) {
|
||||
cache_set->insert(bbto->block_cache.get());
|
||||
}
|
||||
}
|
||||
iopts_map->clear();
|
||||
for (size_t i = 0; i < cf_names.size(); ++i) {
|
||||
iopts_map->insert({cf_names[i], iopts[i]});
|
||||
}
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
uint64_t DBImpl::TEST_FindMinLogContainingOutstandingPrep() {
|
||||
@@ -328,5 +322,49 @@ size_t DBImpl::TEST_EstimateInMemoryStatsHistorySize() const {
|
||||
InstrumentedMutexLock l(&const_cast<DBImpl*>(this)->stats_history_mutex_);
|
||||
return EstimateInMemoryStatsHistorySize();
|
||||
}
|
||||
|
||||
void DBImpl::TEST_VerifyNoObsoleteFilesCached(
|
||||
bool db_mutex_already_held) const {
|
||||
// This check is somewhat expensive and obscure to make a part of every
|
||||
// unit test in every build variety. Thus, we only enable it for ASAN builds.
|
||||
if (!kMustFreeHeapAllocations) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::optional<InstrumentedMutexLock> l;
|
||||
if (db_mutex_already_held) {
|
||||
mutex_.AssertHeld();
|
||||
} else {
|
||||
l.emplace(&mutex_);
|
||||
}
|
||||
|
||||
std::vector<uint64_t> live_files;
|
||||
for (auto cfd : *versions_->GetColumnFamilySet()) {
|
||||
if (cfd->IsDropped()) {
|
||||
continue;
|
||||
}
|
||||
// Sneakily add both SST and blob files to the same list
|
||||
cfd->current()->AddLiveFiles(&live_files, &live_files);
|
||||
}
|
||||
std::sort(live_files.begin(), live_files.end());
|
||||
|
||||
auto fn = [&live_files](const Slice& key, Cache::ObjectPtr, size_t,
|
||||
const Cache::CacheItemHelper* helper) {
|
||||
if (helper != BlobFileCache::GetHelper()) {
|
||||
// Skip non-blob files for now
|
||||
// FIXME: diagnose and fix the leaks of obsolete SST files revealed in
|
||||
// unit tests.
|
||||
return;
|
||||
}
|
||||
// See TableCache and BlobFileCache
|
||||
assert(key.size() == sizeof(uint64_t));
|
||||
uint64_t file_number;
|
||||
GetUnaligned(reinterpret_cast<const uint64_t*>(key.data()), &file_number);
|
||||
// Assert file is in sorted live_files
|
||||
assert(
|
||||
std::binary_search(live_files.begin(), live_files.end(), file_number));
|
||||
};
|
||||
table_cache_->ApplyToAllEntries(fn, {});
|
||||
}
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
#endif // NDEBUG
|
||||
@@ -43,6 +43,14 @@ uint64_t DBImpl::GetObsoleteSstFilesSize() {
|
||||
return versions_->GetObsoleteSstFilesSize();
|
||||
}
|
||||
|
||||
uint64_t DBImpl::MinOptionsFileNumberToKeep() {
|
||||
mutex_.AssertHeld();
|
||||
if (!min_options_file_numbers_.empty()) {
|
||||
return *min_options_file_numbers_.begin();
|
||||
}
|
||||
return std::numeric_limits<uint64_t>::max();
|
||||
}
|
||||
|
||||
Status DBImpl::DisableFileDeletions() {
|
||||
Status s;
|
||||
int my_disable_delete_obsolete_files;
|
||||
@@ -147,6 +155,7 @@ void DBImpl::FindObsoleteFiles(JobContext* job_context, bool force,
|
||||
// here but later find newer generated unfinalized files while scanning.
|
||||
job_context->min_pending_output = MinObsoleteSstNumberToKeep();
|
||||
job_context->files_to_quarantine = error_handler_.GetFilesToQuarantine();
|
||||
job_context->min_options_file_number = MinOptionsFileNumberToKeep();
|
||||
|
||||
// Get obsolete files. This function will also update the list of
|
||||
// pending files in VersionSet().
|
||||
@@ -498,7 +507,7 @@ void DBImpl::PurgeObsoleteFiles(JobContext& state, bool schedule_only) {
|
||||
dbname_);
|
||||
|
||||
// File numbers of most recent two OPTIONS file in candidate_files (found in
|
||||
// previos FindObsoleteFiles(full_scan=true))
|
||||
// previous FindObsoleteFiles(full_scan=true))
|
||||
// At this point, there must not be any duplicate file numbers in
|
||||
// candidate_files.
|
||||
uint64_t optsfile_num1 = std::numeric_limits<uint64_t>::min();
|
||||
@@ -519,6 +528,11 @@ void DBImpl::PurgeObsoleteFiles(JobContext& state, bool schedule_only) {
|
||||
}
|
||||
}
|
||||
|
||||
// For remote compactions, we need to keep OPTIONS file that may get
|
||||
// referenced by the remote worker
|
||||
|
||||
optsfile_num2 = std::min(optsfile_num2, state.min_options_file_number);
|
||||
|
||||
// Close WALs before trying to delete them.
|
||||
for (const auto w : state.logs_to_free) {
|
||||
// TODO: maybe check the return value of Close.
|
||||
@@ -966,7 +980,8 @@ void DBImpl::SetDBId(std::string&& id, bool read_only,
|
||||
}
|
||||
|
||||
Status DBImpl::SetupDBId(const WriteOptions& write_options, bool read_only,
|
||||
bool is_new_db, VersionEdit* version_edit) {
|
||||
bool is_new_db, bool is_retry,
|
||||
VersionEdit* version_edit) {
|
||||
Status s;
|
||||
if (!is_new_db) {
|
||||
// Check for the IDENTITY file and create it if not there or
|
||||
@@ -974,7 +989,11 @@ Status DBImpl::SetupDBId(const WriteOptions& write_options, bool read_only,
|
||||
std::string db_id_in_file;
|
||||
s = fs_->FileExists(IdentityFileName(dbname_), IOOptions(), nullptr);
|
||||
if (s.ok()) {
|
||||
s = GetDbIdentityFromIdentityFile(&db_id_in_file);
|
||||
IOOptions opts;
|
||||
if (is_retry) {
|
||||
opts.verify_and_reconstruct_read = true;
|
||||
}
|
||||
s = GetDbIdentityFromIdentityFile(opts, &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
|
||||
|
||||
+25
-14
@@ -301,7 +301,7 @@ Status DBImpl::NewDB(std::vector<std::string>* new_filenames) {
|
||||
VersionEdit new_db_edit;
|
||||
const WriteOptions write_options(Env::IOActivity::kDBOpen);
|
||||
Status s = SetupDBId(write_options, /*read_only=*/false, /*is_new_db=*/true,
|
||||
&new_db_edit);
|
||||
/*is_retry=*/false, &new_db_edit);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -675,11 +675,11 @@ Status DBImpl::Recover(
|
||||
// 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);
|
||||
s = SetupDBId(write_options, read_only, is_new_db, is_retry, &edit);
|
||||
recovery_ctx->UpdateVersionEdits(
|
||||
versions_->GetColumnFamilySet()->GetDefault(), edit);
|
||||
} else {
|
||||
s = SetupDBId(write_options, read_only, is_new_db, nullptr);
|
||||
s = SetupDBId(write_options, read_only, is_new_db, is_retry, nullptr);
|
||||
}
|
||||
assert(!s.ok() || !db_id_.empty());
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log, "DB ID: %s\n", db_id_.c_str());
|
||||
@@ -1274,7 +1274,8 @@ Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& wal_numbers,
|
||||
reader.GetRecordedTimestampSize();
|
||||
status = HandleWriteBatchTimestampSizeDifference(
|
||||
&batch, running_ts_sz, record_ts_sz,
|
||||
TimestampSizeConsistencyMode::kReconcileInconsistency, &new_batch);
|
||||
TimestampSizeConsistencyMode::kReconcileInconsistency, seq_per_batch_,
|
||||
batch_per_txn_, &new_batch);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
@@ -1667,10 +1668,19 @@ Status DBImpl::WriteLevel0TableForRecovery(int job_id, ColumnFamilyData* cfd,
|
||||
Arena arena;
|
||||
Status s;
|
||||
TableProperties table_properties;
|
||||
const auto* ucmp = cfd->internal_comparator().user_comparator();
|
||||
assert(ucmp);
|
||||
const size_t ts_sz = ucmp->timestamp_size();
|
||||
const bool logical_strip_timestamp =
|
||||
ts_sz > 0 && !cfd->ioptions()->persist_user_defined_timestamps;
|
||||
{
|
||||
ScopedArenaPtr<InternalIterator> iter(
|
||||
mem->NewIterator(ro, /*seqno_to_time_mapping=*/nullptr, &arena,
|
||||
/*prefix_extractor=*/nullptr));
|
||||
logical_strip_timestamp
|
||||
? mem->NewTimestampStrippingIterator(
|
||||
ro, /*seqno_to_time_mapping=*/nullptr, &arena,
|
||||
/*prefix_extractor=*/nullptr, ts_sz)
|
||||
: 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",
|
||||
@@ -1705,11 +1715,14 @@ Status DBImpl::WriteLevel0TableForRecovery(int job_id, ColumnFamilyData* cfd,
|
||||
std::vector<std::unique_ptr<FragmentedRangeTombstoneIterator>>
|
||||
range_del_iters;
|
||||
auto range_del_iter =
|
||||
// This is called during recovery, where a live memtable is flushed
|
||||
// directly. In this case, no fragmented tombstone list is cached in
|
||||
// this memtable yet.
|
||||
mem->NewRangeTombstoneIterator(ro, kMaxSequenceNumber,
|
||||
false /* immutable_memtable */);
|
||||
logical_strip_timestamp
|
||||
? mem->NewTimestampStrippingRangeTombstoneIterator(
|
||||
ro, kMaxSequenceNumber, ts_sz)
|
||||
// This is called during recovery, where a live memtable is
|
||||
// flushed directly. In this case, no fragmented tombstone list is
|
||||
// cached in this memtable yet.
|
||||
: mem->NewRangeTombstoneIterator(ro, kMaxSequenceNumber,
|
||||
false /* immutable_memtable */);
|
||||
if (range_del_iter != nullptr) {
|
||||
range_del_iters.emplace_back(range_del_iter);
|
||||
}
|
||||
@@ -1795,9 +1808,7 @@ Status DBImpl::WriteLevel0TableForRecovery(int job_id, ColumnFamilyData* cfd,
|
||||
|
||||
// For UDT in memtable only feature, move up the cutoff timestamp whenever
|
||||
// a flush happens.
|
||||
const Comparator* ucmp = cfd->user_comparator();
|
||||
size_t ts_sz = ucmp->timestamp_size();
|
||||
if (ts_sz > 0 && !cfd->ioptions()->persist_user_defined_timestamps) {
|
||||
if (logical_strip_timestamp) {
|
||||
Slice mem_newest_udt = mem->GetNewestUDT();
|
||||
std::string full_history_ts_low = cfd->GetFullHistoryTsLow();
|
||||
if (full_history_ts_low.empty() ||
|
||||
|
||||
@@ -265,7 +265,8 @@ Status OpenForReadOnlyCheckExistence(const DBOptions& db_options,
|
||||
const std::shared_ptr<FileSystem>& fs = db_options.env->GetFileSystem();
|
||||
std::string manifest_path;
|
||||
uint64_t manifest_file_number;
|
||||
s = VersionSet::GetCurrentManifestPath(dbname, fs.get(), &manifest_path,
|
||||
s = VersionSet::GetCurrentManifestPath(dbname, fs.get(), /*is_retry=*/false,
|
||||
&manifest_path,
|
||||
&manifest_file_number);
|
||||
} else {
|
||||
// Historic behavior that doesn't necessarily make sense
|
||||
|
||||
@@ -233,7 +233,8 @@ Status DBImplSecondary::RecoverLogFiles(
|
||||
reader->GetRecordedTimestampSize();
|
||||
status = HandleWriteBatchTimestampSizeDifference(
|
||||
&batch, running_ts_sz, record_ts_sz,
|
||||
TimestampSizeConsistencyMode::kVerifyConsistency);
|
||||
TimestampSizeConsistencyMode::kVerifyConsistency, seq_per_batch_,
|
||||
batch_per_txn_);
|
||||
if (!status.ok()) {
|
||||
break;
|
||||
}
|
||||
@@ -951,21 +952,23 @@ Status DB::OpenAndCompact(
|
||||
return s;
|
||||
}
|
||||
|
||||
// 2. Load the options from latest OPTIONS file
|
||||
// 2. Load the options
|
||||
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,
|
||||
|
||||
TEST_SYNC_POINT_CALLBACK(
|
||||
"DBImplSecondary::OpenAndCompact::BeforeLoadingOptions:0",
|
||||
&compaction_input.options_file_number);
|
||||
TEST_SYNC_POINT("DBImplSecondary::OpenAndCompact::BeforeLoadingOptions:1");
|
||||
std::string options_file_name =
|
||||
OptionsFileName(name, compaction_input.options_file_number);
|
||||
|
||||
s = LoadOptionsFromFile(config_options, options_file_name, &db_options,
|
||||
&all_column_families);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
// 3. Override pointer configurations in DBOptions with
|
||||
|
||||
+33
-24
@@ -735,17 +735,6 @@ Status DBImpl::PipelinedWriteImpl(const WriteOptions& write_options,
|
||||
size_t total_byte_size = 0;
|
||||
|
||||
if (w.status.ok()) {
|
||||
// TODO: this use of operator bool on `tracer_` can avoid unnecessary lock
|
||||
// grabs but does not seem thread-safe.
|
||||
if (tracer_) {
|
||||
InstrumentedMutexLock lock(&trace_mutex_);
|
||||
if (tracer_ != nullptr && tracer_->IsWriteOrderPreserved()) {
|
||||
for (auto* writer : wal_write_group) {
|
||||
// TODO: maybe handle the tracing status?
|
||||
tracer_->Write(writer->batch).PermitUncheckedError();
|
||||
}
|
||||
}
|
||||
}
|
||||
SequenceNumber next_sequence = current_sequence;
|
||||
for (auto* writer : wal_write_group) {
|
||||
assert(writer);
|
||||
@@ -760,6 +749,22 @@ Status DBImpl::PipelinedWriteImpl(const WriteOptions& write_options,
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO: this use of operator bool on `tracer_` can avoid unnecessary lock
|
||||
// grabs but does not seem thread-safe.
|
||||
if (tracer_) {
|
||||
InstrumentedMutexLock lock(&trace_mutex_);
|
||||
if (tracer_ != nullptr && tracer_->IsWriteOrderPreserved()) {
|
||||
for (auto* writer : wal_write_group) {
|
||||
if (writer->CallbackFailed()) {
|
||||
// When optimisitc txn conflict checking fails, we should
|
||||
// not record to trace.
|
||||
continue;
|
||||
}
|
||||
// TODO: maybe handle the tracing status?
|
||||
tracer_->Write(writer->batch).PermitUncheckedError();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (w.disable_wal) {
|
||||
has_unpersisted_data_.store(true, std::memory_order_relaxed);
|
||||
}
|
||||
@@ -1005,19 +1010,6 @@ Status DBImpl::WriteImplWALOnly(
|
||||
WriteThread::WriteGroup write_group;
|
||||
uint64_t last_sequence;
|
||||
write_thread->EnterAsBatchGroupLeader(&w, &write_group);
|
||||
// Note: no need to update last_batch_group_size_ here since the batch writes
|
||||
// to WAL only
|
||||
// TODO: this use of operator bool on `tracer_` can avoid unnecessary lock
|
||||
// grabs but does not seem thread-safe.
|
||||
if (tracer_) {
|
||||
InstrumentedMutexLock lock(&trace_mutex_);
|
||||
if (tracer_ != nullptr && tracer_->IsWriteOrderPreserved()) {
|
||||
for (auto* writer : write_group) {
|
||||
// TODO: maybe handle the tracing status?
|
||||
tracer_->Write(writer->batch).PermitUncheckedError();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size_t pre_release_callback_cnt = 0;
|
||||
size_t total_byte_size = 0;
|
||||
@@ -1032,6 +1024,23 @@ Status DBImpl::WriteImplWALOnly(
|
||||
}
|
||||
}
|
||||
|
||||
// Note: no need to update last_batch_group_size_ here since the batch writes
|
||||
// to WAL only
|
||||
// TODO: this use of operator bool on `tracer_` can avoid unnecessary lock
|
||||
// grabs but does not seem thread-safe.
|
||||
if (tracer_) {
|
||||
InstrumentedMutexLock lock(&trace_mutex_);
|
||||
if (tracer_ != nullptr && tracer_->IsWriteOrderPreserved()) {
|
||||
for (auto* writer : write_group) {
|
||||
if (writer->CallbackFailed()) {
|
||||
continue;
|
||||
}
|
||||
// TODO: maybe handle the tracing status?
|
||||
tracer_->Write(writer->batch).PermitUncheckedError();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const bool concurrent_update = true;
|
||||
// Update stats while we are an exclusive group leader, so we know
|
||||
// that nobody else can be writing to these particular stats.
|
||||
|
||||
+161
-9
@@ -27,12 +27,14 @@ class CorruptionFS : public FileSystemWrapper {
|
||||
num_writable_file_errors_(0),
|
||||
corruption_trigger_(INT_MAX),
|
||||
read_count_(0),
|
||||
corrupt_offset_(0),
|
||||
corrupt_len_(0),
|
||||
rnd_(300),
|
||||
fs_buffer_(fs_buffer),
|
||||
verify_read_(verify_read) {}
|
||||
~CorruptionFS() override {
|
||||
// Assert that the corruption was reset, which means it got triggered
|
||||
assert(corruption_trigger_ == INT_MAX);
|
||||
assert(corruption_trigger_ == INT_MAX || corrupt_len_ > 0);
|
||||
}
|
||||
const char* Name() const override { return "ErrorEnv"; }
|
||||
|
||||
@@ -48,8 +50,10 @@ class CorruptionFS : public FileSystemWrapper {
|
||||
}
|
||||
|
||||
void SetCorruptionTrigger(const int trigger) {
|
||||
MutexLock l(&mutex_);
|
||||
corruption_trigger_ = trigger;
|
||||
read_count_ = 0;
|
||||
corrupt_fname_.clear();
|
||||
}
|
||||
|
||||
IOStatus NewRandomAccessFile(const std::string& fname,
|
||||
@@ -58,25 +62,31 @@ class CorruptionFS : public FileSystemWrapper {
|
||||
IODebugContext* dbg) override {
|
||||
class CorruptionRandomAccessFile : public FSRandomAccessFileOwnerWrapper {
|
||||
public:
|
||||
CorruptionRandomAccessFile(CorruptionFS& fs,
|
||||
CorruptionRandomAccessFile(CorruptionFS& fs, const std::string& fname,
|
||||
std::unique_ptr<FSRandomAccessFile>& file)
|
||||
: FSRandomAccessFileOwnerWrapper(std::move(file)), fs_(fs) {}
|
||||
: FSRandomAccessFileOwnerWrapper(std::move(file)),
|
||||
fs_(fs),
|
||||
fname_(fname) {}
|
||||
|
||||
IOStatus Read(uint64_t offset, size_t len, const IOOptions& opts,
|
||||
Slice* result, char* scratch,
|
||||
IODebugContext* dbg) const override {
|
||||
IOStatus s = target()->Read(offset, len, opts, result, scratch, dbg);
|
||||
if (opts.verify_and_reconstruct_read) {
|
||||
fs_.MaybeResetOverlapWithCorruptedChunk(fname_, offset,
|
||||
result->size());
|
||||
return s;
|
||||
}
|
||||
|
||||
MutexLock l(&fs_.mutex_);
|
||||
if (s.ok() && ++fs_.read_count_ >= fs_.corruption_trigger_) {
|
||||
fs_.read_count_ = 0;
|
||||
fs_.corruption_trigger_ = INT_MAX;
|
||||
char* data = const_cast<char*>(result->data());
|
||||
std::memcpy(
|
||||
data,
|
||||
fs_.rnd_.RandomString(static_cast<int>(result->size())).c_str(),
|
||||
result->size());
|
||||
fs_.SetCorruptedChunk(fname_, offset, result->size());
|
||||
}
|
||||
return s;
|
||||
}
|
||||
@@ -101,14 +111,76 @@ class CorruptionFS : public FileSystemWrapper {
|
||||
return IOStatus::OK();
|
||||
}
|
||||
|
||||
IOStatus Prefetch(uint64_t /*offset*/, size_t /*n*/,
|
||||
const IOOptions& /*options*/,
|
||||
IODebugContext* /*dbg*/) override {
|
||||
return IOStatus::NotSupported("Prefetch");
|
||||
}
|
||||
|
||||
private:
|
||||
CorruptionFS& fs_;
|
||||
std::string fname_;
|
||||
};
|
||||
|
||||
std::unique_ptr<FSRandomAccessFile> file;
|
||||
IOStatus s = target()->NewRandomAccessFile(fname, opts, &file, dbg);
|
||||
EXPECT_OK(s);
|
||||
result->reset(new CorruptionRandomAccessFile(*this, file));
|
||||
result->reset(new CorruptionRandomAccessFile(*this, fname, file));
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
IOStatus NewSequentialFile(const std::string& fname,
|
||||
const FileOptions& file_opts,
|
||||
std::unique_ptr<FSSequentialFile>* result,
|
||||
IODebugContext* dbg) override {
|
||||
class CorruptionSequentialFile : public FSSequentialFileOwnerWrapper {
|
||||
public:
|
||||
CorruptionSequentialFile(CorruptionFS& fs, const std::string& fname,
|
||||
std::unique_ptr<FSSequentialFile>& file)
|
||||
: FSSequentialFileOwnerWrapper(std::move(file)),
|
||||
fs_(fs),
|
||||
fname_(fname),
|
||||
offset_(0) {}
|
||||
|
||||
IOStatus Read(size_t len, const IOOptions& opts, Slice* result,
|
||||
char* scratch, IODebugContext* dbg) override {
|
||||
IOStatus s = target()->Read(len, opts, result, scratch, dbg);
|
||||
if (result->size() == 0 ||
|
||||
fname_.find("IDENTITY") != std::string::npos) {
|
||||
return s;
|
||||
}
|
||||
|
||||
if (opts.verify_and_reconstruct_read) {
|
||||
fs_.MaybeResetOverlapWithCorruptedChunk(fname_, offset_,
|
||||
result->size());
|
||||
return s;
|
||||
}
|
||||
|
||||
MutexLock l(&fs_.mutex_);
|
||||
if (s.ok() && ++fs_.read_count_ >= fs_.corruption_trigger_) {
|
||||
fs_.corruption_trigger_ = INT_MAX;
|
||||
char* data = const_cast<char*>(result->data());
|
||||
std::memcpy(
|
||||
data,
|
||||
fs_.rnd_.RandomString(static_cast<int>(result->size())).c_str(),
|
||||
result->size());
|
||||
fs_.SetCorruptedChunk(fname_, offset_, result->size());
|
||||
}
|
||||
offset_ += result->size();
|
||||
return s;
|
||||
}
|
||||
|
||||
private:
|
||||
CorruptionFS& fs_;
|
||||
std::string fname_;
|
||||
size_t offset_;
|
||||
};
|
||||
|
||||
std::unique_ptr<FSSequentialFile> file;
|
||||
IOStatus s = target()->NewSequentialFile(fname, file_opts, &file, dbg);
|
||||
EXPECT_OK(s);
|
||||
result->reset(new CorruptionSequentialFile(*this, fname, file));
|
||||
|
||||
return s;
|
||||
}
|
||||
@@ -123,12 +195,40 @@ class CorruptionFS : public FileSystemWrapper {
|
||||
}
|
||||
}
|
||||
|
||||
void SetCorruptedChunk(const std::string& fname, size_t offset, size_t len) {
|
||||
assert(corrupt_fname_.empty());
|
||||
|
||||
corrupt_fname_ = fname;
|
||||
corrupt_offset_ = offset;
|
||||
corrupt_len_ = len;
|
||||
}
|
||||
|
||||
void MaybeResetOverlapWithCorruptedChunk(const std::string& fname,
|
||||
size_t offset, size_t len) {
|
||||
if (fname == corrupt_fname_ &&
|
||||
((offset <= corrupt_offset_ && (offset + len) > corrupt_offset_) ||
|
||||
(offset >= corrupt_offset_ &&
|
||||
offset < (corrupt_offset_ + corrupt_len_)))) {
|
||||
corrupt_fname_.clear();
|
||||
}
|
||||
}
|
||||
|
||||
bool VerifyRetry() { return corrupt_len_ > 0 && corrupt_fname_.empty(); }
|
||||
|
||||
int read_count() { return read_count_; }
|
||||
|
||||
int corruption_trigger() { return corruption_trigger_; }
|
||||
|
||||
private:
|
||||
int corruption_trigger_;
|
||||
int read_count_;
|
||||
std::string corrupt_fname_;
|
||||
size_t corrupt_offset_;
|
||||
size_t corrupt_len_;
|
||||
Random rnd_;
|
||||
bool fs_buffer_;
|
||||
bool verify_read_;
|
||||
port::Mutex mutex_;
|
||||
};
|
||||
} // anonymous namespace
|
||||
|
||||
@@ -717,6 +817,7 @@ class DBIOCorruptionTest
|
||||
bbto.num_file_reads_for_auto_readahead = 0;
|
||||
options_.table_factory.reset(NewBlockBasedTableFactory(bbto));
|
||||
options_.disable_auto_compactions = true;
|
||||
options_.max_file_opening_threads = 0;
|
||||
|
||||
Reopen(options_);
|
||||
}
|
||||
@@ -857,8 +958,8 @@ TEST_P(DBIOCorruptionTest, FlushReadCorruptionRetry) {
|
||||
Status s = Flush();
|
||||
if (std::get<2>(GetParam())) {
|
||||
ASSERT_OK(s);
|
||||
ASSERT_EQ(stats()->getTickerCount(FILE_READ_CORRUPTION_RETRY_COUNT), 1);
|
||||
ASSERT_EQ(stats()->getTickerCount(FILE_READ_CORRUPTION_RETRY_SUCCESS_COUNT),
|
||||
ASSERT_GT(stats()->getTickerCount(FILE_READ_CORRUPTION_RETRY_COUNT), 1);
|
||||
ASSERT_GT(stats()->getTickerCount(FILE_READ_CORRUPTION_RETRY_SUCCESS_COUNT),
|
||||
1);
|
||||
|
||||
std::string val;
|
||||
@@ -885,8 +986,8 @@ TEST_P(DBIOCorruptionTest, ManifestCorruptionRetry) {
|
||||
|
||||
if (std::get<2>(GetParam())) {
|
||||
ASSERT_OK(ReopenDB());
|
||||
ASSERT_EQ(stats()->getTickerCount(FILE_READ_CORRUPTION_RETRY_COUNT), 1);
|
||||
ASSERT_EQ(stats()->getTickerCount(FILE_READ_CORRUPTION_RETRY_SUCCESS_COUNT),
|
||||
ASSERT_GT(stats()->getTickerCount(FILE_READ_CORRUPTION_RETRY_COUNT), 1);
|
||||
ASSERT_GT(stats()->getTickerCount(FILE_READ_CORRUPTION_RETRY_SUCCESS_COUNT),
|
||||
1);
|
||||
} else {
|
||||
ASSERT_EQ(ReopenDB(), Status::Corruption());
|
||||
@@ -970,6 +1071,57 @@ TEST_P(DBIOCorruptionTest, TablePropertiesCorruptionRetry) {
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
|
||||
TEST_P(DBIOCorruptionTest, DBOpenReadCorruptionRetry) {
|
||||
if (!std::get<2>(GetParam())) {
|
||||
return;
|
||||
}
|
||||
CorruptionFS* fs =
|
||||
static_cast<CorruptionFS*>(env_guard_->GetFileSystem().get());
|
||||
|
||||
for (int sst = 0; sst < 3; ++sst) {
|
||||
for (int key = 0; key < 100; ++key) {
|
||||
std::stringstream ss;
|
||||
ss << std::setw(3) << 100 * sst + key;
|
||||
ASSERT_OK(Put("key" + ss.str(), "val" + ss.str()));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
Close();
|
||||
|
||||
// DB open will create table readers unless we reduce the table cache
|
||||
// capacity.
|
||||
// SanitizeOptions will set max_open_files to minimum of 20. Table cache
|
||||
// is allocated with max_open_files - 10 as capacity. So override
|
||||
// max_open_files to 11 so table cache capacity will become 1. This will
|
||||
// prevent file open during DB open and force the file to be opened
|
||||
// during MultiGet
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"SanitizeOptions::AfterChangeMaxOpenFiles", [&](void* arg) {
|
||||
int* max_open_files = (int*)arg;
|
||||
*max_open_files = 11;
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
// Progressively increase the IO count trigger for corruption, and verify
|
||||
// that it was retried
|
||||
int corruption_trigger = 1;
|
||||
fs->SetCorruptionTrigger(corruption_trigger);
|
||||
do {
|
||||
fs->SetCorruptionTrigger(corruption_trigger);
|
||||
ASSERT_OK(ReopenDB());
|
||||
for (int sst = 0; sst < 3; ++sst) {
|
||||
for (int key = 0; key < 100; ++key) {
|
||||
std::stringstream ss;
|
||||
ss << std::setw(3) << 100 * sst + key;
|
||||
ASSERT_EQ(Get("key" + ss.str()), "val" + ss.str());
|
||||
}
|
||||
}
|
||||
// Verify that the injected corruption was repaired
|
||||
ASSERT_TRUE(fs->VerifyRetry());
|
||||
corruption_trigger++;
|
||||
} while (fs->corruption_trigger() == INT_MAX);
|
||||
}
|
||||
|
||||
// The parameters are - 1. Use FS provided buffer, 2. Use async IO ReadOption,
|
||||
// 3. Retry with verify_and_reconstruct_read IOOption
|
||||
INSTANTIATE_TEST_CASE_P(DBIOCorruptionTest, DBIOCorruptionTest,
|
||||
|
||||
+113
-85
@@ -52,7 +52,9 @@ DBIter::DBIter(Env* _env, const ReadOptions& read_options,
|
||||
user_comparator_(cmp),
|
||||
merge_operator_(ioptions.merge_operator.get()),
|
||||
iter_(iter),
|
||||
version_(version),
|
||||
blob_reader_(version, read_options.read_tier,
|
||||
read_options.verify_checksums, read_options.fill_cache,
|
||||
read_options.io_activity),
|
||||
read_callback_(read_callback),
|
||||
sequence_(s),
|
||||
statistics_(ioptions.stats),
|
||||
@@ -71,13 +73,10 @@ DBIter::DBIter(Env* _env, const ReadOptions& read_options,
|
||||
expect_total_order_inner_iter_(prefix_extractor_ == nullptr ||
|
||||
read_options.total_order_seek ||
|
||||
read_options.auto_prefix_mode),
|
||||
read_tier_(read_options.read_tier),
|
||||
fill_cache_(read_options.fill_cache),
|
||||
verify_checksums_(read_options.verify_checksums),
|
||||
expose_blob_index_(expose_blob_index),
|
||||
allow_unprepared_value_(read_options.allow_unprepared_value),
|
||||
is_blob_(false),
|
||||
arena_mode_(arena_mode),
|
||||
io_activity_(read_options.io_activity),
|
||||
cfh_(cfh),
|
||||
timestamp_ub_(read_options.timestamp),
|
||||
timestamp_lb_(read_options.iter_start_ts),
|
||||
@@ -151,7 +150,7 @@ void DBIter::Next() {
|
||||
PERF_CPU_TIMER_GUARD(iter_next_cpu_nanos, clock_);
|
||||
// Release temporarily pinned blocks from last operation
|
||||
ReleaseTempPinnedData();
|
||||
ResetBlobValue();
|
||||
ResetBlobData();
|
||||
ResetValueAndColumns();
|
||||
local_stats_.skip_count_ += num_internal_keys_skipped_;
|
||||
local_stats_.skip_count_--;
|
||||
@@ -194,29 +193,21 @@ void DBIter::Next() {
|
||||
}
|
||||
}
|
||||
|
||||
bool DBIter::SetBlobValueIfNeeded(const Slice& user_key,
|
||||
const Slice& blob_index) {
|
||||
assert(!is_blob_);
|
||||
Status DBIter::BlobReader::RetrieveAndSetBlobValue(const Slice& user_key,
|
||||
const Slice& blob_index) {
|
||||
assert(blob_value_.empty());
|
||||
|
||||
if (expose_blob_index_) { // Stacked BlobDB implementation
|
||||
is_blob_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!version_) {
|
||||
status_ = Status::Corruption("Encountered unexpected blob index.");
|
||||
valid_ = false;
|
||||
return false;
|
||||
return Status::Corruption("Encountered unexpected blob index.");
|
||||
}
|
||||
|
||||
// TODO: consider moving ReadOptions from ArenaWrappedDBIter to DBIter to
|
||||
// avoid having to copy options back and forth.
|
||||
// TODO: plumb Env::IOActivity, Env::IOPriority
|
||||
// TODO: plumb Env::IOPriority
|
||||
ReadOptions read_options;
|
||||
read_options.read_tier = read_tier_;
|
||||
read_options.fill_cache = fill_cache_;
|
||||
read_options.verify_checksums = verify_checksums_;
|
||||
read_options.fill_cache = fill_cache_;
|
||||
read_options.io_activity = io_activity_;
|
||||
constexpr FilePrefetchBuffer* prefetch_buffer = nullptr;
|
||||
constexpr uint64_t* bytes_read = nullptr;
|
||||
@@ -224,16 +215,51 @@ bool DBIter::SetBlobValueIfNeeded(const Slice& user_key,
|
||||
const Status s = version_->GetBlob(read_options, user_key, blob_index,
|
||||
prefetch_buffer, &blob_value_, bytes_read);
|
||||
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
bool DBIter::SetValueAndColumnsFromBlobImpl(const Slice& user_key,
|
||||
const Slice& blob_index) {
|
||||
const Status s = blob_reader_.RetrieveAndSetBlobValue(user_key, blob_index);
|
||||
if (!s.ok()) {
|
||||
status_ = s;
|
||||
valid_ = false;
|
||||
is_blob_ = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
is_blob_ = true;
|
||||
SetValueAndColumnsFromPlain(blob_reader_.GetBlobValue());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DBIter::SetValueAndColumnsFromBlob(const Slice& user_key,
|
||||
const Slice& blob_index) {
|
||||
assert(!is_blob_);
|
||||
is_blob_ = true;
|
||||
|
||||
if (expose_blob_index_) {
|
||||
SetValueAndColumnsFromPlain(blob_index);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (allow_unprepared_value_) {
|
||||
assert(value_.empty());
|
||||
assert(wide_columns_.empty());
|
||||
|
||||
assert(lazy_blob_index_.empty());
|
||||
lazy_blob_index_ = blob_index;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return SetValueAndColumnsFromBlobImpl(user_key, blob_index);
|
||||
}
|
||||
|
||||
bool DBIter::SetValueAndColumnsFromEntity(Slice slice) {
|
||||
assert(value_.empty());
|
||||
assert(wide_columns_.empty());
|
||||
@@ -279,6 +305,24 @@ bool DBIter::SetValueAndColumnsFromMergeResult(const Status& merge_status,
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DBIter::PrepareValue() {
|
||||
assert(valid_);
|
||||
|
||||
if (lazy_blob_index_.empty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
assert(allow_unprepared_value_);
|
||||
assert(is_blob_);
|
||||
|
||||
const bool result =
|
||||
SetValueAndColumnsFromBlobImpl(saved_key_.GetUserKey(), lazy_blob_index_);
|
||||
|
||||
lazy_blob_index_.clear();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// PRE: saved_key_ has the current user key if skipping_saved_key
|
||||
// POST: saved_key_ should have the next user key if valid_,
|
||||
// if the current entry is a result of merge
|
||||
@@ -408,7 +452,7 @@ bool DBIter::FindNextUserEntryInternal(bool skipping_saved_key,
|
||||
case kTypeValuePreferredSeqno:
|
||||
case kTypeBlobIndex:
|
||||
case kTypeWideColumnEntity:
|
||||
if (!PrepareValue()) {
|
||||
if (!PrepareValueInternal()) {
|
||||
return false;
|
||||
}
|
||||
if (timestamp_lb_) {
|
||||
@@ -420,12 +464,9 @@ bool DBIter::FindNextUserEntryInternal(bool skipping_saved_key,
|
||||
}
|
||||
|
||||
if (ikey_.type == kTypeBlobIndex) {
|
||||
if (!SetBlobValueIfNeeded(ikey_.user_key, iter_.value())) {
|
||||
if (!SetValueAndColumnsFromBlob(ikey_.user_key, iter_.value())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SetValueAndColumnsFromPlain(expose_blob_index_ ? iter_.value()
|
||||
: blob_value_);
|
||||
} else if (ikey_.type == kTypeWideColumnEntity) {
|
||||
if (!SetValueAndColumnsFromEntity(iter_.value())) {
|
||||
return false;
|
||||
@@ -445,7 +486,7 @@ bool DBIter::FindNextUserEntryInternal(bool skipping_saved_key,
|
||||
return true;
|
||||
break;
|
||||
case kTypeMerge:
|
||||
if (!PrepareValue()) {
|
||||
if (!PrepareValueInternal()) {
|
||||
return false;
|
||||
}
|
||||
saved_key_.SetUserKey(
|
||||
@@ -540,6 +581,8 @@ bool DBIter::FindNextUserEntryInternal(bool skipping_saved_key,
|
||||
} else {
|
||||
iter_.Next();
|
||||
}
|
||||
// This could be a long-running operation due to tombstones, etc.
|
||||
ROCKSDB_THREAD_YIELD_HOOK();
|
||||
} while (iter_.Valid());
|
||||
|
||||
valid_ = false;
|
||||
@@ -590,7 +633,7 @@ bool DBIter::MergeValuesNewToOld() {
|
||||
iter_.Next();
|
||||
break;
|
||||
}
|
||||
if (!PrepareValue()) {
|
||||
if (!PrepareValueInternal()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -619,23 +662,9 @@ bool DBIter::MergeValuesNewToOld() {
|
||||
iter_.value(), iter_.iter()->IsValuePinned() /* operand_pinned */);
|
||||
PERF_COUNTER_ADD(internal_merge_count, 1);
|
||||
} else if (kTypeBlobIndex == ikey.type) {
|
||||
if (expose_blob_index_) {
|
||||
status_ =
|
||||
Status::NotSupported("BlobDB does not support merge operator.");
|
||||
valid_ = false;
|
||||
if (!MergeWithBlobBaseValue(iter_.value(), ikey.user_key)) {
|
||||
return false;
|
||||
}
|
||||
// hit a put, merge the put value with operands and store the
|
||||
// final result in saved_value_. We are done!
|
||||
if (!SetBlobValueIfNeeded(ikey.user_key, iter_.value())) {
|
||||
return false;
|
||||
}
|
||||
valid_ = true;
|
||||
if (!MergeWithPlainBaseValue(blob_value_, ikey.user_key)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ResetBlobValue();
|
||||
|
||||
// iter_ is positioned after put
|
||||
iter_.Next();
|
||||
@@ -643,6 +672,7 @@ bool DBIter::MergeValuesNewToOld() {
|
||||
valid_ = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
} else if (kTypeWideColumnEntity == ikey.type) {
|
||||
if (!MergeWithWideColumnBaseValue(iter_.value(), ikey.user_key)) {
|
||||
@@ -689,7 +719,7 @@ void DBIter::Prev() {
|
||||
PERF_COUNTER_ADD(iter_prev_count, 1);
|
||||
PERF_CPU_TIMER_GUARD(iter_prev_cpu_nanos, clock_);
|
||||
ReleaseTempPinnedData();
|
||||
ResetBlobValue();
|
||||
ResetBlobData();
|
||||
ResetValueAndColumns();
|
||||
ResetInternalKeysSkippedCounter();
|
||||
bool ok = true;
|
||||
@@ -926,7 +956,7 @@ bool DBIter::FindValueForCurrentKey() {
|
||||
return FindValueForCurrentKeyUsingSeek();
|
||||
}
|
||||
|
||||
if (!PrepareValue()) {
|
||||
if (!PrepareValueInternal()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1041,21 +1071,9 @@ bool DBIter::FindValueForCurrentKey() {
|
||||
}
|
||||
return true;
|
||||
} else if (last_not_merge_type == kTypeBlobIndex) {
|
||||
if (expose_blob_index_) {
|
||||
status_ =
|
||||
Status::NotSupported("BlobDB does not support merge operator.");
|
||||
valid_ = false;
|
||||
if (!MergeWithBlobBaseValue(pinned_value_, saved_key_.GetUserKey())) {
|
||||
return false;
|
||||
}
|
||||
if (!SetBlobValueIfNeeded(saved_key_.GetUserKey(), pinned_value_)) {
|
||||
return false;
|
||||
}
|
||||
valid_ = true;
|
||||
if (!MergeWithPlainBaseValue(blob_value_, saved_key_.GetUserKey())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ResetBlobValue();
|
||||
|
||||
return true;
|
||||
} else if (last_not_merge_type == kTypeWideColumnEntity) {
|
||||
@@ -1080,13 +1098,9 @@ bool DBIter::FindValueForCurrentKey() {
|
||||
|
||||
break;
|
||||
case kTypeBlobIndex:
|
||||
if (!SetBlobValueIfNeeded(saved_key_.GetUserKey(), pinned_value_)) {
|
||||
if (!SetValueAndColumnsFromBlob(saved_key_.GetUserKey(), pinned_value_)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SetValueAndColumnsFromPlain(expose_blob_index_ ? pinned_value_
|
||||
: blob_value_);
|
||||
|
||||
break;
|
||||
case kTypeWideColumnEntity:
|
||||
if (!SetValueAndColumnsFromEntity(pinned_value_)) {
|
||||
@@ -1173,7 +1187,7 @@ bool DBIter::FindValueForCurrentKeyUsingSeek() {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (!PrepareValue()) {
|
||||
if (!PrepareValueInternal()) {
|
||||
return false;
|
||||
}
|
||||
if (timestamp_size_ > 0) {
|
||||
@@ -1190,12 +1204,9 @@ bool DBIter::FindValueForCurrentKeyUsingSeek() {
|
||||
pinned_value_ = iter_.value();
|
||||
}
|
||||
if (ikey.type == kTypeBlobIndex) {
|
||||
if (!SetBlobValueIfNeeded(ikey.user_key, pinned_value_)) {
|
||||
if (!SetValueAndColumnsFromBlob(ikey.user_key, pinned_value_)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SetValueAndColumnsFromPlain(expose_blob_index_ ? pinned_value_
|
||||
: blob_value_);
|
||||
} else if (ikey.type == kTypeWideColumnEntity) {
|
||||
if (!SetValueAndColumnsFromEntity(pinned_value_)) {
|
||||
return false;
|
||||
@@ -1243,7 +1254,7 @@ bool DBIter::FindValueForCurrentKeyUsingSeek() {
|
||||
ikey.type == kTypeDeletionWithTimestamp) {
|
||||
break;
|
||||
}
|
||||
if (!PrepareValue()) {
|
||||
if (!PrepareValueInternal()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1261,21 +1272,9 @@ bool DBIter::FindValueForCurrentKeyUsingSeek() {
|
||||
iter_.value(), iter_.iter()->IsValuePinned() /* operand_pinned */);
|
||||
PERF_COUNTER_ADD(internal_merge_count, 1);
|
||||
} else if (ikey.type == kTypeBlobIndex) {
|
||||
if (expose_blob_index_) {
|
||||
status_ =
|
||||
Status::NotSupported("BlobDB does not support merge operator.");
|
||||
valid_ = false;
|
||||
if (!MergeWithBlobBaseValue(iter_.value(), saved_key_.GetUserKey())) {
|
||||
return false;
|
||||
}
|
||||
if (!SetBlobValueIfNeeded(ikey.user_key, iter_.value())) {
|
||||
return false;
|
||||
}
|
||||
valid_ = true;
|
||||
if (!MergeWithPlainBaseValue(blob_value_, saved_key_.GetUserKey())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ResetBlobValue();
|
||||
|
||||
return true;
|
||||
} else if (ikey.type == kTypeWideColumnEntity) {
|
||||
@@ -1342,6 +1341,35 @@ bool DBIter::MergeWithPlainBaseValue(const Slice& value,
|
||||
return SetValueAndColumnsFromMergeResult(s, result_type);
|
||||
}
|
||||
|
||||
bool DBIter::MergeWithBlobBaseValue(const Slice& blob_index,
|
||||
const Slice& user_key) {
|
||||
assert(!is_blob_);
|
||||
|
||||
if (expose_blob_index_) {
|
||||
status_ =
|
||||
Status::NotSupported("Legacy BlobDB does not support merge operator.");
|
||||
valid_ = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
const Status s = blob_reader_.RetrieveAndSetBlobValue(user_key, blob_index);
|
||||
if (!s.ok()) {
|
||||
status_ = s;
|
||||
valid_ = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
valid_ = true;
|
||||
|
||||
if (!MergeWithPlainBaseValue(blob_reader_.GetBlobValue(), user_key)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
blob_reader_.ResetBlobValue();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DBIter::MergeWithWideColumnBaseValue(const Slice& entity,
|
||||
const Slice& user_key) {
|
||||
// `op_failure_scope` (an output parameter) is not provided (set to nullptr)
|
||||
@@ -1531,7 +1559,7 @@ void DBIter::Seek(const Slice& target) {
|
||||
|
||||
status_ = Status::OK();
|
||||
ReleaseTempPinnedData();
|
||||
ResetBlobValue();
|
||||
ResetBlobData();
|
||||
ResetValueAndColumns();
|
||||
ResetInternalKeysSkippedCounter();
|
||||
|
||||
@@ -1607,7 +1635,7 @@ void DBIter::SeekForPrev(const Slice& target) {
|
||||
|
||||
status_ = Status::OK();
|
||||
ReleaseTempPinnedData();
|
||||
ResetBlobValue();
|
||||
ResetBlobData();
|
||||
ResetValueAndColumns();
|
||||
ResetInternalKeysSkippedCounter();
|
||||
|
||||
@@ -1668,7 +1696,7 @@ void DBIter::SeekToFirst() {
|
||||
status_.PermitUncheckedError();
|
||||
direction_ = kForward;
|
||||
ReleaseTempPinnedData();
|
||||
ResetBlobValue();
|
||||
ResetBlobData();
|
||||
ResetValueAndColumns();
|
||||
ResetInternalKeysSkippedCounter();
|
||||
ClearSavedValue();
|
||||
@@ -1731,7 +1759,7 @@ void DBIter::SeekToLast() {
|
||||
status_.PermitUncheckedError();
|
||||
direction_ = kReverse;
|
||||
ReleaseTempPinnedData();
|
||||
ResetBlobValue();
|
||||
ResetBlobData();
|
||||
ResetValueAndColumns();
|
||||
ResetInternalKeysSkippedCounter();
|
||||
ClearSavedValue();
|
||||
|
||||
+44
-17
@@ -218,7 +218,34 @@ class DBIter final : public Iterator {
|
||||
}
|
||||
void set_valid(bool v) { valid_ = v; }
|
||||
|
||||
bool PrepareValue() override;
|
||||
|
||||
private:
|
||||
class BlobReader {
|
||||
public:
|
||||
BlobReader(const Version* version, ReadTier read_tier,
|
||||
bool verify_checksums, bool fill_cache,
|
||||
Env::IOActivity io_activity)
|
||||
: version_(version),
|
||||
read_tier_(read_tier),
|
||||
verify_checksums_(verify_checksums),
|
||||
fill_cache_(fill_cache),
|
||||
io_activity_(io_activity) {}
|
||||
|
||||
const Slice& GetBlobValue() const { return blob_value_; }
|
||||
Status RetrieveAndSetBlobValue(const Slice& user_key,
|
||||
const Slice& blob_index);
|
||||
void ResetBlobValue() { blob_value_.Reset(); }
|
||||
|
||||
private:
|
||||
PinnableSlice blob_value_;
|
||||
const Version* version_;
|
||||
ReadTier read_tier_;
|
||||
bool verify_checksums_;
|
||||
bool fill_cache_;
|
||||
Env::IOActivity io_activity_;
|
||||
};
|
||||
|
||||
// For all methods in this block:
|
||||
// PRE: iter_->Valid() && status_.ok()
|
||||
// Return false if there was an error, and status() is non-ok, valid_ = false;
|
||||
@@ -299,15 +326,6 @@ class DBIter final : public Iterator {
|
||||
: user_comparator_.CompareWithoutTimestamp(a, b);
|
||||
}
|
||||
|
||||
// Retrieves the blob value for the specified user key using the given blob
|
||||
// index when using the integrated BlobDB implementation.
|
||||
bool SetBlobValueIfNeeded(const Slice& user_key, const Slice& blob_index);
|
||||
|
||||
void ResetBlobValue() {
|
||||
is_blob_ = false;
|
||||
blob_value_.Reset();
|
||||
}
|
||||
|
||||
void SetValueAndColumnsFromPlain(const Slice& slice) {
|
||||
assert(value_.empty());
|
||||
assert(wide_columns_.empty());
|
||||
@@ -316,6 +334,11 @@ class DBIter final : public Iterator {
|
||||
wide_columns_.emplace_back(kDefaultWideColumnName, slice);
|
||||
}
|
||||
|
||||
bool SetValueAndColumnsFromBlobImpl(const Slice& user_key,
|
||||
const Slice& blob_index);
|
||||
bool SetValueAndColumnsFromBlob(const Slice& user_key,
|
||||
const Slice& blob_index);
|
||||
|
||||
bool SetValueAndColumnsFromEntity(Slice slice);
|
||||
|
||||
bool SetValueAndColumnsFromMergeResult(const Status& merge_status,
|
||||
@@ -326,14 +349,21 @@ class DBIter final : public Iterator {
|
||||
wide_columns_.clear();
|
||||
}
|
||||
|
||||
void ResetBlobData() {
|
||||
blob_reader_.ResetBlobValue();
|
||||
lazy_blob_index_.clear();
|
||||
is_blob_ = false;
|
||||
}
|
||||
|
||||
// The following methods perform the actual merge operation for the
|
||||
// no base value/plain base value/wide-column base value cases.
|
||||
// no/plain/blob/wide-column base value cases.
|
||||
// If user-defined timestamp is enabled, `user_key` includes timestamp.
|
||||
bool MergeWithNoBaseValue(const Slice& user_key);
|
||||
bool MergeWithPlainBaseValue(const Slice& value, const Slice& user_key);
|
||||
bool MergeWithBlobBaseValue(const Slice& blob_index, const Slice& user_key);
|
||||
bool MergeWithWideColumnBaseValue(const Slice& entity, const Slice& user_key);
|
||||
|
||||
bool PrepareValue() {
|
||||
bool PrepareValueInternal() {
|
||||
if (!iter_.PrepareValue()) {
|
||||
assert(!iter_.status().ok());
|
||||
valid_ = false;
|
||||
@@ -356,7 +386,7 @@ class DBIter final : public Iterator {
|
||||
UserComparatorWrapper user_comparator_;
|
||||
const MergeOperator* const merge_operator_;
|
||||
IteratorWrapper iter_;
|
||||
const Version* version_;
|
||||
BlobReader blob_reader_;
|
||||
ReadCallback* read_callback_;
|
||||
// Max visible sequence number. It is normally the snapshot seq unless we have
|
||||
// uncommitted data in db as in WriteUnCommitted.
|
||||
@@ -376,7 +406,6 @@ class DBIter final : public Iterator {
|
||||
std::string saved_value_;
|
||||
Slice pinned_value_;
|
||||
// for prefix seek mode to support prev()
|
||||
PinnableSlice blob_value_;
|
||||
// Value of the default column
|
||||
Slice value_;
|
||||
// All columns (i.e. name-value pairs)
|
||||
@@ -410,15 +439,13 @@ class DBIter final : public Iterator {
|
||||
// Expect the inner iterator to maintain a total order.
|
||||
// prefix_extractor_ must be non-NULL if the value is false.
|
||||
const bool expect_total_order_inner_iter_;
|
||||
ReadTier read_tier_;
|
||||
bool fill_cache_;
|
||||
bool verify_checksums_;
|
||||
// Whether the iterator is allowed to expose blob references. Set to true when
|
||||
// the stacked BlobDB implementation is used, false otherwise.
|
||||
bool expose_blob_index_;
|
||||
bool allow_unprepared_value_;
|
||||
Slice lazy_blob_index_;
|
||||
bool is_blob_;
|
||||
bool arena_mode_;
|
||||
const Env::IOActivity io_activity_;
|
||||
// List of operands for merge operator.
|
||||
MergeContext merge_context_;
|
||||
LocalStatistics local_stats_;
|
||||
|
||||
+22
-6
@@ -231,21 +231,33 @@ TEST_F(DBOptionsTest, SetMutableTableOptions) {
|
||||
ASSERT_OK(dbfull()->SetOptions(
|
||||
cfh, {{"table_factory.block_size", "16384"},
|
||||
{"table_factory.block_restart_interval", "11"}}));
|
||||
// Old c_bbto
|
||||
ASSERT_EQ(c_bbto->block_size, 8192);
|
||||
ASSERT_EQ(c_bbto->block_restart_interval, 7);
|
||||
// New c_bbto
|
||||
c_opts = dbfull()->GetOptions(cfh);
|
||||
c_bbto = c_opts.table_factory->GetOptions<BlockBasedTableOptions>();
|
||||
ASSERT_EQ(c_bbto->block_size, 16384);
|
||||
ASSERT_EQ(c_bbto->block_restart_interval, 11);
|
||||
|
||||
// Now set an option that is not mutable - options should not change
|
||||
ASSERT_NOK(
|
||||
dbfull()->SetOptions(cfh, {{"table_factory.no_block_cache", "false"}}));
|
||||
// FIXME: find a way to make this fail again
|
||||
// ASSERT_NOK(
|
||||
// dbfull()->SetOptions(cfh, {{"table_factory.no_block_cache", "false"}}));
|
||||
c_opts = dbfull()->GetOptions(cfh);
|
||||
ASSERT_EQ(c_bbto, c_opts.table_factory->GetOptions<BlockBasedTableOptions>());
|
||||
ASSERT_EQ(c_bbto->no_block_cache, true);
|
||||
ASSERT_EQ(c_bbto->block_size, 16384);
|
||||
ASSERT_EQ(c_bbto->block_restart_interval, 11);
|
||||
|
||||
// Set some that are mutable and some that are not - options should not change
|
||||
ASSERT_NOK(dbfull()->SetOptions(
|
||||
cfh, {{"table_factory.no_block_cache", "false"},
|
||||
{"table_factory.block_size", "8192"},
|
||||
{"table_factory.block_restart_interval", "7"}}));
|
||||
// FIXME: find a way to make this fail again
|
||||
// ASSERT_NOK(dbfull()->SetOptions(
|
||||
// cfh, {{"table_factory.no_block_cache", "false"},
|
||||
// {"table_factory.block_size", "8192"},
|
||||
// {"table_factory.block_restart_interval", "7"}}));
|
||||
c_opts = dbfull()->GetOptions(cfh);
|
||||
ASSERT_EQ(c_bbto, c_opts.table_factory->GetOptions<BlockBasedTableOptions>());
|
||||
ASSERT_EQ(c_bbto->no_block_cache, true);
|
||||
ASSERT_EQ(c_bbto->block_size, 16384);
|
||||
ASSERT_EQ(c_bbto->block_restart_interval, 11);
|
||||
@@ -256,6 +268,8 @@ TEST_F(DBOptionsTest, SetMutableTableOptions) {
|
||||
cfh, {{"table_factory.block_size", "8192"},
|
||||
{"table_factory.does_not_exist", "true"},
|
||||
{"table_factory.block_restart_interval", "7"}}));
|
||||
c_opts = dbfull()->GetOptions(cfh);
|
||||
ASSERT_EQ(c_bbto, c_opts.table_factory->GetOptions<BlockBasedTableOptions>());
|
||||
ASSERT_EQ(c_bbto->no_block_cache, true);
|
||||
ASSERT_EQ(c_bbto->block_size, 16384);
|
||||
ASSERT_EQ(c_bbto->block_restart_interval, 11);
|
||||
@@ -271,6 +285,7 @@ TEST_F(DBOptionsTest, SetMutableTableOptions) {
|
||||
{"table_factory.block_restart_interval", "13"}}));
|
||||
c_opts = dbfull()->GetOptions(cfh);
|
||||
ASSERT_EQ(c_opts.blob_file_size, 32768);
|
||||
c_bbto = c_opts.table_factory->GetOptions<BlockBasedTableOptions>();
|
||||
ASSERT_EQ(c_bbto->block_size, 16384);
|
||||
ASSERT_EQ(c_bbto->block_restart_interval, 13);
|
||||
// Set some on the table and a bad one on the ColumnFamily - options should
|
||||
@@ -279,6 +294,7 @@ TEST_F(DBOptionsTest, SetMutableTableOptions) {
|
||||
cfh, {{"table_factory.block_size", "1024"},
|
||||
{"no_such_option", "32768"},
|
||||
{"table_factory.block_restart_interval", "7"}}));
|
||||
ASSERT_EQ(c_bbto, c_opts.table_factory->GetOptions<BlockBasedTableOptions>());
|
||||
ASSERT_EQ(c_bbto->block_size, 16384);
|
||||
ASSERT_EQ(c_bbto->block_restart_interval, 13);
|
||||
}
|
||||
|
||||
@@ -244,7 +244,7 @@ TEST_F(DBSecondaryTest, SimpleInternalCompaction) {
|
||||
ASSERT_EQ(largest.user_key().ToString(), "foo");
|
||||
ASSERT_EQ(result.output_level, 1);
|
||||
ASSERT_EQ(result.output_path, this->secondary_path_);
|
||||
ASSERT_EQ(result.num_output_records, 2);
|
||||
ASSERT_EQ(result.stats.num_output_records, 2);
|
||||
ASSERT_GT(result.bytes_written, 0);
|
||||
ASSERT_OK(result.status);
|
||||
}
|
||||
|
||||
+48
-16
@@ -1826,21 +1826,30 @@ TEST_F(DBTest, GetApproximateMemTableStats) {
|
||||
uint64_t count;
|
||||
uint64_t size;
|
||||
|
||||
// Because Random::GetTLSInstance() seed is reset in DBTestBase,
|
||||
// this test is deterministic.
|
||||
|
||||
std::string start = Key(50);
|
||||
std::string end = Key(60);
|
||||
Range r(start, end);
|
||||
db_->GetApproximateMemTableStats(r, &count, &size);
|
||||
ASSERT_GT(count, 0);
|
||||
ASSERT_LE(count, N);
|
||||
ASSERT_GT(size, 6000);
|
||||
ASSERT_LT(size, 204800);
|
||||
// When actual count is <= 10, it returns that as the minimum
|
||||
EXPECT_EQ(count, 10);
|
||||
EXPECT_EQ(size, 10440);
|
||||
|
||||
start = Key(20);
|
||||
end = Key(100);
|
||||
r = Range(start, end);
|
||||
db_->GetApproximateMemTableStats(r, &count, &size);
|
||||
EXPECT_EQ(count, 72);
|
||||
EXPECT_EQ(size, 75168);
|
||||
|
||||
start = Key(500);
|
||||
end = Key(600);
|
||||
r = Range(start, end);
|
||||
db_->GetApproximateMemTableStats(r, &count, &size);
|
||||
ASSERT_EQ(count, 0);
|
||||
ASSERT_EQ(size, 0);
|
||||
EXPECT_EQ(count, 0);
|
||||
EXPECT_EQ(size, 0);
|
||||
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
@@ -1848,8 +1857,8 @@ TEST_F(DBTest, GetApproximateMemTableStats) {
|
||||
end = Key(60);
|
||||
r = Range(start, end);
|
||||
db_->GetApproximateMemTableStats(r, &count, &size);
|
||||
ASSERT_EQ(count, 0);
|
||||
ASSERT_EQ(size, 0);
|
||||
EXPECT_EQ(count, 0);
|
||||
EXPECT_EQ(size, 0);
|
||||
|
||||
for (int i = 0; i < N; i++) {
|
||||
ASSERT_OK(Put(Key(1000 + i), rnd.RandomString(1024)));
|
||||
@@ -1857,10 +1866,11 @@ TEST_F(DBTest, GetApproximateMemTableStats) {
|
||||
|
||||
start = Key(100);
|
||||
end = Key(1020);
|
||||
// Actually 20 keys in the range ^^
|
||||
r = Range(start, end);
|
||||
db_->GetApproximateMemTableStats(r, &count, &size);
|
||||
ASSERT_GT(count, 20);
|
||||
ASSERT_GT(size, 6000);
|
||||
EXPECT_EQ(count, 20);
|
||||
EXPECT_EQ(size, 20880);
|
||||
}
|
||||
|
||||
TEST_F(DBTest, ApproximateSizes) {
|
||||
@@ -5169,10 +5179,14 @@ TEST_F(DBTest, DynamicLevelCompressionPerLevel) {
|
||||
options.max_bytes_for_level_multiplier = 4;
|
||||
options.max_background_compactions = 1;
|
||||
options.num_levels = 5;
|
||||
options.statistics = CreateDBStatistics();
|
||||
|
||||
options.compression_per_level.resize(3);
|
||||
// No compression for L0
|
||||
options.compression_per_level[0] = kNoCompression;
|
||||
// No compression for the Ln whre L0 is compacted to
|
||||
options.compression_per_level[1] = kNoCompression;
|
||||
// Snpapy compression for Ln+1
|
||||
options.compression_per_level[2] = kSnappyCompression;
|
||||
|
||||
OnFileDeletionListener* listener = new OnFileDeletionListener();
|
||||
@@ -5181,7 +5195,7 @@ TEST_F(DBTest, DynamicLevelCompressionPerLevel) {
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// Insert more than 80K. L4 should be base level. Neither L0 nor L4 should
|
||||
// be compressed, so total data size should be more than 80K.
|
||||
// be compressed, so there shouldn't be any compression.
|
||||
for (int i = 0; i < 20; i++) {
|
||||
ASSERT_OK(Put(Key(keys[i]), CompressibleString(&rnd, 4000)));
|
||||
}
|
||||
@@ -5191,10 +5205,17 @@ TEST_F(DBTest, DynamicLevelCompressionPerLevel) {
|
||||
ASSERT_EQ(NumTableFilesAtLevel(1), 0);
|
||||
ASSERT_EQ(NumTableFilesAtLevel(2), 0);
|
||||
ASSERT_EQ(NumTableFilesAtLevel(3), 0);
|
||||
// Assuming each files' metadata is at least 50 bytes/
|
||||
ASSERT_GT(SizeAtLevel(0) + SizeAtLevel(4), 20U * 4000U + 50U * 4);
|
||||
ASSERT_TRUE(NumTableFilesAtLevel(0) > 0 || NumTableFilesAtLevel(4) > 0);
|
||||
|
||||
// Insert 400KB. Some data will be compressed
|
||||
// Verify there was no compression
|
||||
auto num_block_compressed =
|
||||
options.statistics->getTickerCount(NUMBER_BLOCK_COMPRESSED);
|
||||
ASSERT_EQ(num_block_compressed, 0);
|
||||
|
||||
// Insert 400KB and there will be some files end up in L3. According to the
|
||||
// above compression settings for each level, there will be some compression.
|
||||
ASSERT_OK(options.statistics->Reset());
|
||||
ASSERT_EQ(num_block_compressed, 0);
|
||||
for (int i = 21; i < 120; i++) {
|
||||
ASSERT_OK(Put(Key(keys[i]), CompressibleString(&rnd, 4000)));
|
||||
}
|
||||
@@ -5202,9 +5223,14 @@ TEST_F(DBTest, DynamicLevelCompressionPerLevel) {
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
ASSERT_EQ(NumTableFilesAtLevel(1), 0);
|
||||
ASSERT_EQ(NumTableFilesAtLevel(2), 0);
|
||||
ASSERT_GE(NumTableFilesAtLevel(3), 1);
|
||||
ASSERT_GE(NumTableFilesAtLevel(4), 1);
|
||||
|
||||
// Verify there was compression
|
||||
num_block_compressed =
|
||||
options.statistics->getTickerCount(NUMBER_BLOCK_COMPRESSED);
|
||||
ASSERT_GT(num_block_compressed, 0);
|
||||
|
||||
ASSERT_LT(SizeAtLevel(0) + SizeAtLevel(3) + SizeAtLevel(4),
|
||||
120U * 4000U + 50U * 24);
|
||||
// Make sure data in files in L3 is not compacted by removing all files
|
||||
// in L4 and calculate number of rows
|
||||
ASSERT_OK(dbfull()->SetOptions({
|
||||
@@ -5224,6 +5250,12 @@ TEST_F(DBTest, DynamicLevelCompressionPerLevel) {
|
||||
num_keys++;
|
||||
}
|
||||
ASSERT_OK(iter->status());
|
||||
|
||||
ASSERT_EQ(NumTableFilesAtLevel(1), 0);
|
||||
ASSERT_EQ(NumTableFilesAtLevel(2), 0);
|
||||
ASSERT_GE(NumTableFilesAtLevel(3), 1);
|
||||
ASSERT_EQ(NumTableFilesAtLevel(4), 0);
|
||||
|
||||
ASSERT_GT(SizeAtLevel(0) + SizeAtLevel(3), num_keys * 4000U + num_keys * 10U);
|
||||
}
|
||||
|
||||
|
||||
+13
-1
@@ -272,11 +272,23 @@ LookupKey::LookupKey(const Slice& _user_key, SequenceNumber s,
|
||||
|
||||
void IterKey::EnlargeBuffer(size_t key_size) {
|
||||
// If size is smaller than buffer size, continue using current buffer,
|
||||
// or the static allocated one, as default
|
||||
// or the inline one, as default
|
||||
assert(key_size > buf_size_);
|
||||
// Need to enlarge the buffer.
|
||||
ResetBuffer();
|
||||
buf_ = new char[key_size];
|
||||
buf_size_ = key_size;
|
||||
}
|
||||
|
||||
void IterKey::EnlargeSecondaryBufferIfNeeded(size_t key_size) {
|
||||
// If size is smaller than buffer size, continue using current buffer,
|
||||
// or the inline one, as default
|
||||
if (key_size <= secondary_buf_size_) {
|
||||
return;
|
||||
}
|
||||
// Need to enlarge the secondary buffer.
|
||||
ResetSecondaryBuffer();
|
||||
secondary_buf_ = new char[key_size];
|
||||
secondary_buf_size_ = key_size;
|
||||
}
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
+137
-55
@@ -10,6 +10,7 @@
|
||||
#pragma once
|
||||
#include <stdio.h>
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
@@ -562,18 +563,28 @@ inline uint64_t GetInternalKeySeqno(const Slice& internal_key) {
|
||||
// allocation for smaller keys.
|
||||
// 3. It tracks user key or internal key, and allow conversion between them.
|
||||
class IterKey {
|
||||
static constexpr size_t kInlineBufferSize = 39;
|
||||
// This is only used by user-defined timestamps in MemTable only feature,
|
||||
// which only supports uint64_t timestamps.
|
||||
static constexpr char kTsMin[] = "\x00\x00\x00\x00\x00\x00\x00\x00";
|
||||
|
||||
public:
|
||||
IterKey()
|
||||
: buf_(space_),
|
||||
key_(buf_),
|
||||
key_size_(0),
|
||||
buf_size_(sizeof(space_)),
|
||||
is_user_key_(true) {}
|
||||
buf_size_(kInlineBufferSize),
|
||||
is_user_key_(true),
|
||||
secondary_buf_(space_for_secondary_buf_),
|
||||
secondary_buf_size_(kInlineBufferSize) {}
|
||||
// No copying allowed
|
||||
IterKey(const IterKey&) = delete;
|
||||
void operator=(const IterKey&) = delete;
|
||||
|
||||
~IterKey() { ResetBuffer(); }
|
||||
~IterKey() {
|
||||
ResetBuffer();
|
||||
ResetSecondaryBuffer();
|
||||
}
|
||||
|
||||
// The bool will be picked up by the next calls to SetKey
|
||||
void SetIsUserKey(bool is_user_key) { is_user_key_ = is_user_key; }
|
||||
@@ -641,13 +652,15 @@ class IterKey {
|
||||
const char* non_shared_data,
|
||||
const size_t non_shared_len,
|
||||
const size_t ts_sz) {
|
||||
std::string kTsMin(ts_sz, static_cast<unsigned char>(0));
|
||||
std::string key_with_ts;
|
||||
std::vector<Slice> key_parts_with_ts;
|
||||
// This function is only used by the UDT in memtable feature, which only
|
||||
// support built in comparators with uint64 timestamps.
|
||||
assert(ts_sz == sizeof(uint64_t));
|
||||
size_t next_key_slice_index = 0;
|
||||
if (IsUserKey()) {
|
||||
key_parts_with_ts = {Slice(key_, shared_len),
|
||||
Slice(non_shared_data, non_shared_len),
|
||||
Slice(kTsMin)};
|
||||
key_slices_[next_key_slice_index++] = Slice(key_, shared_len);
|
||||
key_slices_[next_key_slice_index++] =
|
||||
Slice(non_shared_data, non_shared_len);
|
||||
key_slices_[next_key_slice_index++] = Slice(kTsMin, ts_sz);
|
||||
} else {
|
||||
assert(shared_len + non_shared_len >= kNumInternalBytes);
|
||||
// Invaraint: shared_user_key_len + shared_internal_bytes_len = shared_len
|
||||
@@ -664,30 +677,46 @@ class IterKey {
|
||||
|
||||
// One Slice among the three Slices will get split into two Slices, plus
|
||||
// a timestamp slice.
|
||||
key_parts_with_ts.reserve(5);
|
||||
bool ts_added = false;
|
||||
// Add slice parts and find the right location to add the min timestamp.
|
||||
MaybeAddKeyPartsWithTimestamp(
|
||||
key_, shared_user_key_len,
|
||||
shared_internal_bytes_len + non_shared_len < kNumInternalBytes,
|
||||
shared_len + non_shared_len - kNumInternalBytes, kTsMin,
|
||||
key_parts_with_ts, &ts_added);
|
||||
shared_len + non_shared_len - kNumInternalBytes, ts_sz,
|
||||
&next_key_slice_index, &ts_added);
|
||||
MaybeAddKeyPartsWithTimestamp(
|
||||
key_ + user_key_len, shared_internal_bytes_len,
|
||||
non_shared_len < kNumInternalBytes,
|
||||
shared_internal_bytes_len + non_shared_len - kNumInternalBytes,
|
||||
kTsMin, key_parts_with_ts, &ts_added);
|
||||
shared_internal_bytes_len + non_shared_len - kNumInternalBytes, ts_sz,
|
||||
&next_key_slice_index, &ts_added);
|
||||
MaybeAddKeyPartsWithTimestamp(non_shared_data, non_shared_len,
|
||||
non_shared_len >= kNumInternalBytes,
|
||||
non_shared_len - kNumInternalBytes, kTsMin,
|
||||
key_parts_with_ts, &ts_added);
|
||||
non_shared_len - kNumInternalBytes, ts_sz,
|
||||
&next_key_slice_index, &ts_added);
|
||||
assert(ts_added);
|
||||
}
|
||||
SetKeyImpl(next_key_slice_index,
|
||||
/* total_bytes= */ shared_len + non_shared_len + ts_sz);
|
||||
}
|
||||
|
||||
Slice new_key(SliceParts(&key_parts_with_ts.front(),
|
||||
static_cast<int>(key_parts_with_ts.size())),
|
||||
&key_with_ts);
|
||||
SetKey(new_key);
|
||||
Slice SetKeyWithPaddedMinTimestamp(const Slice& key, size_t ts_sz) {
|
||||
// This function is only used by the UDT in memtable feature, which only
|
||||
// support built in comparators with uint64 timestamps.
|
||||
assert(ts_sz == sizeof(uint64_t));
|
||||
size_t num_key_slices = 0;
|
||||
if (is_user_key_) {
|
||||
key_slices_[0] = key;
|
||||
key_slices_[1] = Slice(kTsMin, ts_sz);
|
||||
num_key_slices = 2;
|
||||
} else {
|
||||
assert(key.size() >= kNumInternalBytes);
|
||||
size_t user_key_size = key.size() - kNumInternalBytes;
|
||||
key_slices_[0] = Slice(key.data(), user_key_size);
|
||||
key_slices_[1] = Slice(kTsMin, ts_sz);
|
||||
key_slices_[2] = Slice(key.data() + user_key_size, kNumInternalBytes);
|
||||
num_key_slices = 3;
|
||||
}
|
||||
return SetKeyImpl(num_key_slices, key.size() + ts_sz);
|
||||
}
|
||||
|
||||
Slice SetKey(const Slice& key, bool copy = true) {
|
||||
@@ -718,15 +747,6 @@ class IterKey {
|
||||
return Slice(key_, key_n);
|
||||
}
|
||||
|
||||
// Copy the key into IterKey own buf_
|
||||
void OwnKey() {
|
||||
assert(IsKeyPinned() == true);
|
||||
|
||||
Reserve(key_size_);
|
||||
memcpy(buf_, key_, key_size_);
|
||||
key_ = buf_;
|
||||
}
|
||||
|
||||
// Update the sequence number in the internal key. Guarantees not to
|
||||
// invalidate slices to the key (and the user key).
|
||||
void UpdateInternalKey(uint64_t seq, ValueType t, const Slice* ts = nullptr) {
|
||||
@@ -738,10 +758,15 @@ class IterKey {
|
||||
ts->size());
|
||||
}
|
||||
uint64_t newval = (seq << 8) | t;
|
||||
EncodeFixed64(&buf_[key_size_ - kNumInternalBytes], newval);
|
||||
if (key_ == buf_) {
|
||||
EncodeFixed64(&buf_[key_size_ - kNumInternalBytes], newval);
|
||||
} else {
|
||||
assert(key_ == secondary_buf_);
|
||||
EncodeFixed64(&secondary_buf_[key_size_ - kNumInternalBytes], newval);
|
||||
}
|
||||
}
|
||||
|
||||
bool IsKeyPinned() const { return (key_ != buf_); }
|
||||
bool IsKeyPinned() const { return key_ != buf_ && key_ != secondary_buf_; }
|
||||
|
||||
// If `ts` is provided, user_key should not contain timestamp,
|
||||
// and `ts` is appended after user_key.
|
||||
@@ -806,8 +831,24 @@ class IterKey {
|
||||
const char* key_;
|
||||
size_t key_size_;
|
||||
size_t buf_size_;
|
||||
char space_[39]; // Avoid allocation for short keys
|
||||
char space_[kInlineBufferSize]; // Avoid allocation for short keys
|
||||
bool is_user_key_;
|
||||
// Below variables are only used by user-defined timestamps in MemTable only
|
||||
// feature for iterating keys in an index block or a data block.
|
||||
//
|
||||
// We will alternate between buf_ and secondary_buf_ to hold the key. key_
|
||||
// will be modified in accordance to point to the right one. This is to avoid
|
||||
// an extra copy when we need to copy some shared bytes from previous key
|
||||
// (delta encoding), and we need to pad a min timestamp at the right location.
|
||||
char space_for_secondary_buf_[kInlineBufferSize]; // Avoid allocation for
|
||||
// short keys
|
||||
char* secondary_buf_;
|
||||
size_t secondary_buf_size_;
|
||||
// Use to track the pieces that together make the whole key. We then copy
|
||||
// these pieces in order either into buf_ or secondary_buf_ depending on where
|
||||
// the previous key is held.
|
||||
std::array<Slice, 5> key_slices_;
|
||||
// End of variables used by user-defined timestamps in MemTable only feature.
|
||||
|
||||
Slice SetKeyImpl(const Slice& key, bool copy) {
|
||||
size_t size = key.size();
|
||||
@@ -824,18 +865,64 @@ class IterKey {
|
||||
return Slice(key_, key_size_);
|
||||
}
|
||||
|
||||
Slice SetKeyImpl(size_t num_key_slices, size_t total_bytes) {
|
||||
assert(num_key_slices <= 5);
|
||||
char* buf_start = nullptr;
|
||||
if (key_ == buf_) {
|
||||
// If the previous key is in buf_, we copy key_slices_ in order into
|
||||
// secondary_buf_.
|
||||
EnlargeSecondaryBufferIfNeeded(total_bytes);
|
||||
buf_start = secondary_buf_;
|
||||
key_ = secondary_buf_;
|
||||
} else {
|
||||
// Copy key_slices_ in order into buf_.
|
||||
EnlargeBufferIfNeeded(total_bytes);
|
||||
buf_start = buf_;
|
||||
key_ = buf_;
|
||||
}
|
||||
#ifndef NDEBUG
|
||||
size_t actual_total_bytes = 0;
|
||||
#endif // NDEBUG
|
||||
for (size_t i = 0; i < num_key_slices; i++) {
|
||||
size_t key_slice_size = key_slices_[i].size();
|
||||
memcpy(buf_start, key_slices_[i].data(), key_slice_size);
|
||||
buf_start += key_slice_size;
|
||||
#ifndef NDEBUG
|
||||
actual_total_bytes += key_slice_size;
|
||||
#endif // NDEBUG
|
||||
}
|
||||
#ifndef NDEBUG
|
||||
assert(actual_total_bytes == total_bytes);
|
||||
#endif // NDEBUG
|
||||
key_size_ = total_bytes;
|
||||
return Slice(key_, key_size_);
|
||||
}
|
||||
|
||||
void ResetBuffer() {
|
||||
if (key_ == buf_) {
|
||||
key_size_ = 0;
|
||||
}
|
||||
if (buf_ != space_) {
|
||||
delete[] buf_;
|
||||
buf_ = space_;
|
||||
}
|
||||
buf_size_ = sizeof(space_);
|
||||
key_size_ = 0;
|
||||
buf_size_ = kInlineBufferSize;
|
||||
}
|
||||
|
||||
void ResetSecondaryBuffer() {
|
||||
if (key_ == secondary_buf_) {
|
||||
key_size_ = 0;
|
||||
}
|
||||
if (secondary_buf_ != space_for_secondary_buf_) {
|
||||
delete[] secondary_buf_;
|
||||
secondary_buf_ = space_for_secondary_buf_;
|
||||
}
|
||||
secondary_buf_size_ = kInlineBufferSize;
|
||||
}
|
||||
|
||||
// Enlarge the buffer size if needed based on key_size.
|
||||
// By default, static allocated buffer is used. Once there is a key
|
||||
// larger than the static allocated buffer, another buffer is dynamically
|
||||
// By default, inline buffer is used. Once there is a key
|
||||
// larger than the inline buffer, another buffer is dynamically
|
||||
// allocated, until a larger key buffer is requested. In that case, we
|
||||
// reallocate buffer and delete the old one.
|
||||
void EnlargeBufferIfNeeded(size_t key_size) {
|
||||
@@ -846,23 +933,27 @@ class IterKey {
|
||||
}
|
||||
}
|
||||
|
||||
void EnlargeSecondaryBufferIfNeeded(size_t key_size);
|
||||
|
||||
void EnlargeBuffer(size_t key_size);
|
||||
|
||||
void MaybeAddKeyPartsWithTimestamp(const char* slice_data,
|
||||
const size_t slice_sz, bool add_timestamp,
|
||||
const size_t left_sz,
|
||||
const std::string& min_timestamp,
|
||||
std::vector<Slice>& key_parts,
|
||||
const size_t left_sz, const size_t ts_sz,
|
||||
size_t* next_key_slice_idx,
|
||||
bool* ts_added) {
|
||||
assert(next_key_slice_idx);
|
||||
if (add_timestamp && !*ts_added) {
|
||||
assert(slice_sz >= left_sz);
|
||||
key_parts.emplace_back(slice_data, left_sz);
|
||||
key_parts.emplace_back(min_timestamp);
|
||||
key_parts.emplace_back(slice_data + left_sz, slice_sz - left_sz);
|
||||
key_slices_[(*next_key_slice_idx)++] = Slice(slice_data, left_sz);
|
||||
key_slices_[(*next_key_slice_idx)++] = Slice(kTsMin, ts_sz);
|
||||
key_slices_[(*next_key_slice_idx)++] =
|
||||
Slice(slice_data + left_sz, slice_sz - left_sz);
|
||||
*ts_added = true;
|
||||
} else {
|
||||
key_parts.emplace_back(slice_data, slice_sz);
|
||||
key_slices_[(*next_key_slice_idx)++] = Slice(slice_data, slice_sz);
|
||||
}
|
||||
assert(*next_key_slice_idx <= 5);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -936,22 +1027,13 @@ struct RangeTombstone {
|
||||
// User-defined timestamp is enabled, `sk` and `ek` should be user key
|
||||
// with timestamp, `ts` will replace the timestamps in `sk` and
|
||||
// `ek`.
|
||||
// When `logical_strip_timestamp` is true, the timestamps in `sk` and `ek`
|
||||
// will be replaced with min timestamp.
|
||||
RangeTombstone(Slice sk, Slice ek, SequenceNumber sn, Slice ts,
|
||||
bool logical_strip_timestamp)
|
||||
: seq_(sn) {
|
||||
RangeTombstone(Slice sk, Slice ek, SequenceNumber sn, Slice ts) : seq_(sn) {
|
||||
const size_t ts_sz = ts.size();
|
||||
assert(ts_sz > 0);
|
||||
pinned_start_key_.reserve(sk.size());
|
||||
pinned_end_key_.reserve(ek.size());
|
||||
if (logical_strip_timestamp) {
|
||||
AppendUserKeyWithMinTimestamp(&pinned_start_key_, sk, ts_sz);
|
||||
AppendUserKeyWithMinTimestamp(&pinned_end_key_, ek, ts_sz);
|
||||
} else {
|
||||
AppendUserKeyWithDifferentTimestamp(&pinned_start_key_, sk, ts);
|
||||
AppendUserKeyWithDifferentTimestamp(&pinned_end_key_, ek, ts);
|
||||
}
|
||||
AppendUserKeyWithDifferentTimestamp(&pinned_start_key_, sk, ts);
|
||||
AppendUserKeyWithDifferentTimestamp(&pinned_end_key_, ek, ts);
|
||||
start_key_ = pinned_start_key_;
|
||||
end_key_ = pinned_end_key_;
|
||||
ts_ = Slice(pinned_start_key_.data() + sk.size() - ts_sz, ts_sz);
|
||||
|
||||
@@ -1790,8 +1790,8 @@ TEST_F(ExternalSSTFileBasicTest, OverlappingFiles) {
|
||||
SstFileWriter sst_file_writer(EnvOptions(), options);
|
||||
std::string file1 = sst_files_dir_ + "file1.sst";
|
||||
ASSERT_OK(sst_file_writer.Open(file1));
|
||||
ASSERT_OK(sst_file_writer.Put("a", "z"));
|
||||
ASSERT_OK(sst_file_writer.Put("i", "m"));
|
||||
ASSERT_OK(sst_file_writer.Put("a", "a1"));
|
||||
ASSERT_OK(sst_file_writer.Put("i", "i1"));
|
||||
ExternalSstFileInfo file1_info;
|
||||
ASSERT_OK(sst_file_writer.Finish(&file1_info));
|
||||
files.push_back(std::move(file1));
|
||||
@@ -1800,16 +1800,29 @@ TEST_F(ExternalSSTFileBasicTest, OverlappingFiles) {
|
||||
SstFileWriter sst_file_writer(EnvOptions(), options);
|
||||
std::string file2 = sst_files_dir_ + "file2.sst";
|
||||
ASSERT_OK(sst_file_writer.Open(file2));
|
||||
ASSERT_OK(sst_file_writer.Put("i", "k"));
|
||||
ASSERT_OK(sst_file_writer.Put("i", "i2"));
|
||||
ExternalSstFileInfo file2_info;
|
||||
ASSERT_OK(sst_file_writer.Finish(&file2_info));
|
||||
files.push_back(std::move(file2));
|
||||
}
|
||||
|
||||
{
|
||||
SstFileWriter sst_file_writer(EnvOptions(), options);
|
||||
std::string file3 = sst_files_dir_ + "file3.sst";
|
||||
ASSERT_OK(sst_file_writer.Open(file3));
|
||||
ASSERT_OK(sst_file_writer.Put("j", "j1"));
|
||||
ASSERT_OK(sst_file_writer.Put("m", "m1"));
|
||||
ExternalSstFileInfo file3_info;
|
||||
ASSERT_OK(sst_file_writer.Finish(&file3_info));
|
||||
files.push_back(std::move(file3));
|
||||
}
|
||||
|
||||
IngestExternalFileOptions ifo;
|
||||
ASSERT_OK(db_->IngestExternalFile(files, ifo));
|
||||
ASSERT_EQ(Get("a"), "z");
|
||||
ASSERT_EQ(Get("i"), "k");
|
||||
ASSERT_EQ(Get("a"), "a1");
|
||||
ASSERT_EQ(Get("i"), "i2");
|
||||
ASSERT_EQ(Get("j"), "j1");
|
||||
ASSERT_EQ(Get("m"), "m1");
|
||||
|
||||
int total_keys = 0;
|
||||
Iterator* iter = db_->NewIterator(ReadOptions());
|
||||
@@ -1817,10 +1830,355 @@ TEST_F(ExternalSSTFileBasicTest, OverlappingFiles) {
|
||||
ASSERT_OK(iter->status());
|
||||
total_keys++;
|
||||
}
|
||||
ASSERT_OK(iter->status());
|
||||
delete iter;
|
||||
ASSERT_EQ(total_keys, 2);
|
||||
ASSERT_EQ(total_keys, 4);
|
||||
|
||||
ASSERT_EQ(2, NumTableFilesAtLevel(0));
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(6));
|
||||
ASSERT_EQ(2, NumTableFilesAtLevel(5));
|
||||
}
|
||||
|
||||
TEST_F(ExternalSSTFileBasicTest, AtomicReplaceDataWithStandaloneRangeDeletion) {
|
||||
Options options = CurrentOptions();
|
||||
options.compaction_style = CompactionStyle::kCompactionStyleUniversal;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
std::vector<std::string> files;
|
||||
{
|
||||
// Writes first version of data in range partitioned files.
|
||||
SstFileWriter sst_file_writer(EnvOptions(), options);
|
||||
std::string file1 = sst_files_dir_ + "file1.sst";
|
||||
ASSERT_OK(sst_file_writer.Open(file1));
|
||||
ASSERT_OK(sst_file_writer.Put("a", "a1"));
|
||||
ASSERT_OK(sst_file_writer.Put("b", "b1"));
|
||||
ExternalSstFileInfo file1_info;
|
||||
ASSERT_OK(sst_file_writer.Finish(&file1_info));
|
||||
files.push_back(std::move(file1));
|
||||
|
||||
std::string file2 = sst_files_dir_ + "file2.sst";
|
||||
ASSERT_OK(sst_file_writer.Open(file2));
|
||||
ASSERT_OK(sst_file_writer.Put("x", "x1"));
|
||||
ASSERT_OK(sst_file_writer.Put("y", "y1"));
|
||||
ExternalSstFileInfo file2_info;
|
||||
ASSERT_OK(sst_file_writer.Finish(&file2_info));
|
||||
files.push_back(std::move(file2));
|
||||
}
|
||||
|
||||
IngestExternalFileOptions ifo;
|
||||
ASSERT_OK(db_->IngestExternalFile(files, ifo));
|
||||
ASSERT_EQ(Get("a"), "a1");
|
||||
ASSERT_EQ(Get("b"), "b1");
|
||||
ASSERT_EQ(Get("x"), "x1");
|
||||
ASSERT_EQ(Get("y"), "y1");
|
||||
ASSERT_EQ(2, NumTableFilesAtLevel(6));
|
||||
|
||||
{
|
||||
// Atomically delete old version of data with one range delete file.
|
||||
// And a new batch of range partitioned files with new version of data.
|
||||
files.clear();
|
||||
SstFileWriter sst_file_writer(EnvOptions(), options);
|
||||
std::string file2 = sst_files_dir_ + "file2.sst";
|
||||
ASSERT_OK(sst_file_writer.Open(file2));
|
||||
ASSERT_OK(sst_file_writer.DeleteRange("a", "z"));
|
||||
ExternalSstFileInfo file2_info;
|
||||
ASSERT_OK(sst_file_writer.Finish(&file2_info));
|
||||
files.push_back(std::move(file2));
|
||||
|
||||
std::string file3 = sst_files_dir_ + "file3.sst";
|
||||
ASSERT_OK(sst_file_writer.Open(file3));
|
||||
ASSERT_OK(sst_file_writer.Put("a", "a2"));
|
||||
ASSERT_OK(sst_file_writer.Put("b", "b2"));
|
||||
ExternalSstFileInfo file3_info;
|
||||
ASSERT_OK(sst_file_writer.Finish(&file3_info));
|
||||
files.push_back(std::move(file3));
|
||||
|
||||
std::string file4 = sst_files_dir_ + "file4.sst";
|
||||
ASSERT_OK(sst_file_writer.Open(file4));
|
||||
ASSERT_OK(sst_file_writer.Put("x", "x2"));
|
||||
ASSERT_OK(sst_file_writer.Put("y", "y2"));
|
||||
ExternalSstFileInfo file4_info;
|
||||
ASSERT_OK(sst_file_writer.Finish(&file4_info));
|
||||
files.push_back(std::move(file4));
|
||||
}
|
||||
|
||||
const Snapshot* snapshot = db_->GetSnapshot();
|
||||
|
||||
auto seqno_before_ingestion = db_->GetLatestSequenceNumber();
|
||||
ASSERT_OK(db_->IngestExternalFile(files, ifo));
|
||||
// Overlapping files each occupy one new sequence number.
|
||||
ASSERT_EQ(db_->GetLatestSequenceNumber(), seqno_before_ingestion + 3);
|
||||
|
||||
// Check old version of data, big range deletion, new version of data are
|
||||
// on separate levels.
|
||||
ASSERT_EQ(2, NumTableFilesAtLevel(4));
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(5));
|
||||
ASSERT_EQ(2, NumTableFilesAtLevel(6));
|
||||
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
ASSERT_EQ(2, NumTableFilesAtLevel(4));
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(5));
|
||||
ASSERT_EQ(2, NumTableFilesAtLevel(6));
|
||||
|
||||
bool compaction_iter_input_checked = false;
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"VersionSet::MakeInputIterator:NewCompactionMergingIterator",
|
||||
[&](void* arg) {
|
||||
size_t* num_input_files = static_cast<size_t*>(arg);
|
||||
EXPECT_EQ(1, *num_input_files);
|
||||
compaction_iter_input_checked = true;
|
||||
});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
db_->ReleaseSnapshot(snapshot);
|
||||
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
ASSERT_EQ(2, NumTableFilesAtLevel(4));
|
||||
ASSERT_EQ(0, NumTableFilesAtLevel(5));
|
||||
ASSERT_EQ(0, NumTableFilesAtLevel(6));
|
||||
ASSERT_TRUE(compaction_iter_input_checked);
|
||||
|
||||
ASSERT_EQ(Get("a"), "a2");
|
||||
ASSERT_EQ(Get("b"), "b2");
|
||||
ASSERT_EQ(Get("x"), "x2");
|
||||
ASSERT_EQ(Get("y"), "y2");
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
TEST_F(ExternalSSTFileBasicTest,
|
||||
PartiallyReplaceDataWithOneStandaloneRangeDeletion) {
|
||||
Options options = CurrentOptions();
|
||||
options.compaction_style = CompactionStyle::kCompactionStyleUniversal;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
std::vector<std::string> files;
|
||||
{
|
||||
// Writes first version of data in range partitioned files.
|
||||
SstFileWriter sst_file_writer(EnvOptions(), options);
|
||||
std::string file1 = sst_files_dir_ + "file1.sst";
|
||||
ASSERT_OK(sst_file_writer.Open(file1));
|
||||
ASSERT_OK(sst_file_writer.Put("a", "a1"));
|
||||
ASSERT_OK(sst_file_writer.Put("b", "b1"));
|
||||
ExternalSstFileInfo file1_info;
|
||||
ASSERT_OK(sst_file_writer.Finish(&file1_info));
|
||||
files.push_back(std::move(file1));
|
||||
|
||||
std::string file2 = sst_files_dir_ + "file2.sst";
|
||||
ASSERT_OK(sst_file_writer.Open(file2));
|
||||
ASSERT_OK(sst_file_writer.Put("x", "x1"));
|
||||
ASSERT_OK(sst_file_writer.Put("y", "y"));
|
||||
ExternalSstFileInfo file2_info;
|
||||
ASSERT_OK(sst_file_writer.Finish(&file2_info));
|
||||
files.push_back(std::move(file2));
|
||||
}
|
||||
|
||||
IngestExternalFileOptions ifo;
|
||||
ASSERT_OK(db_->IngestExternalFile(files, ifo));
|
||||
ASSERT_EQ(Get("a"), "a1");
|
||||
ASSERT_EQ(Get("b"), "b1");
|
||||
ASSERT_EQ(Get("x"), "x1");
|
||||
ASSERT_EQ(Get("y"), "y");
|
||||
ASSERT_EQ(2, NumTableFilesAtLevel(6));
|
||||
|
||||
{
|
||||
// Partially delete old version of data with one range delete file. And
|
||||
// add new version of data for deleted range.
|
||||
files.clear();
|
||||
SstFileWriter sst_file_writer(EnvOptions(), options);
|
||||
std::string file2 = sst_files_dir_ + "file2.sst";
|
||||
ASSERT_OK(sst_file_writer.Open(file2));
|
||||
ASSERT_OK(sst_file_writer.DeleteRange("a", "y"));
|
||||
ExternalSstFileInfo file2_info;
|
||||
ASSERT_OK(sst_file_writer.Finish(&file2_info));
|
||||
files.push_back(std::move(file2));
|
||||
std::string file3 = sst_files_dir_ + "file3.sst";
|
||||
ASSERT_OK(sst_file_writer.Open(file3));
|
||||
ASSERT_OK(sst_file_writer.Put("a", "a2"));
|
||||
ASSERT_OK(sst_file_writer.Put("b", "b2"));
|
||||
ExternalSstFileInfo file3_info;
|
||||
ASSERT_OK(sst_file_writer.Finish(&file3_info));
|
||||
files.push_back(std::move(file3));
|
||||
std::string file4 = sst_files_dir_ + "file4.sst";
|
||||
ASSERT_OK(sst_file_writer.Open(file4));
|
||||
ASSERT_OK(sst_file_writer.Put("h", "h1"));
|
||||
ASSERT_OK(sst_file_writer.Put("x", "x2"));
|
||||
ExternalSstFileInfo file4_info;
|
||||
ASSERT_OK(sst_file_writer.Finish(&file4_info));
|
||||
files.push_back(std::move(file4));
|
||||
}
|
||||
|
||||
bool compaction_iter_input_checked = false;
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"VersionSet::MakeInputIterator:NewCompactionMergingIterator",
|
||||
[&](void* arg) {
|
||||
size_t* num_input_files = static_cast<size_t*>(arg);
|
||||
EXPECT_EQ(2, *num_input_files);
|
||||
compaction_iter_input_checked = true;
|
||||
});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
ASSERT_OK(db_->IngestExternalFile(files, ifo));
|
||||
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
ASSERT_EQ(2, NumTableFilesAtLevel(4));
|
||||
ASSERT_EQ(0, NumTableFilesAtLevel(5));
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(6));
|
||||
ASSERT_TRUE(compaction_iter_input_checked);
|
||||
|
||||
ASSERT_EQ(Get("a"), "a2");
|
||||
ASSERT_EQ(Get("b"), "b2");
|
||||
ASSERT_EQ(Get("h"), "h1");
|
||||
ASSERT_EQ(Get("x"), "x2");
|
||||
ASSERT_EQ(Get("y"), "y");
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
TEST_F(ExternalSSTFileBasicTest,
|
||||
PartiallyReplaceDataWithMultipleStandaloneRangeDeletions) {
|
||||
Options options = CurrentOptions();
|
||||
options.compaction_style = CompactionStyle::kCompactionStyleUniversal;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
std::vector<std::string> files;
|
||||
{
|
||||
SstFileWriter sst_file_writer(EnvOptions(), options);
|
||||
std::string file1 = sst_files_dir_ + "file1.sst";
|
||||
ASSERT_OK(sst_file_writer.Open(file1));
|
||||
ASSERT_OK(sst_file_writer.Put("a", "a1"));
|
||||
ExternalSstFileInfo file1_info;
|
||||
ASSERT_OK(sst_file_writer.Finish(&file1_info));
|
||||
files.push_back(std::move(file1));
|
||||
std::string file2 = sst_files_dir_ + "file2.sst";
|
||||
ASSERT_OK(sst_file_writer.Open(file2));
|
||||
ASSERT_OK(sst_file_writer.Put("h", "h"));
|
||||
ExternalSstFileInfo file2_info;
|
||||
ASSERT_OK(sst_file_writer.Finish(&file2_info));
|
||||
files.push_back(std::move(file2));
|
||||
std::string file3 = sst_files_dir_ + "file3.sst";
|
||||
ASSERT_OK(sst_file_writer.Open(file3));
|
||||
ASSERT_OK(sst_file_writer.Put("x", "x1"));
|
||||
ExternalSstFileInfo file3_info;
|
||||
ASSERT_OK(sst_file_writer.Finish(&file3_info));
|
||||
files.push_back(std::move(file3));
|
||||
}
|
||||
|
||||
IngestExternalFileOptions ifo;
|
||||
ASSERT_OK(db_->IngestExternalFile(files, ifo));
|
||||
ASSERT_EQ(Get("a"), "a1");
|
||||
ASSERT_EQ(Get("h"), "h");
|
||||
ASSERT_EQ(Get("x"), "x1");
|
||||
ASSERT_EQ(3, NumTableFilesAtLevel(6));
|
||||
|
||||
{
|
||||
files.clear();
|
||||
SstFileWriter sst_file_writer(EnvOptions(), options);
|
||||
std::string file4 = sst_files_dir_ + "file4.sst";
|
||||
ASSERT_OK(sst_file_writer.Open(file4));
|
||||
ASSERT_OK(sst_file_writer.DeleteRange("a", "b"));
|
||||
ExternalSstFileInfo file4_info;
|
||||
ASSERT_OK(sst_file_writer.Finish(&file4_info));
|
||||
files.push_back(std::move(file4));
|
||||
std::string file5 = sst_files_dir_ + "file5.sst";
|
||||
ASSERT_OK(sst_file_writer.Open(file5));
|
||||
ASSERT_OK(sst_file_writer.DeleteRange("x", "y"));
|
||||
ExternalSstFileInfo file5_info;
|
||||
ASSERT_OK(sst_file_writer.Finish(&file5_info));
|
||||
files.push_back(std::move(file5));
|
||||
std::string file6 = sst_files_dir_ + "file6.sst";
|
||||
ASSERT_OK(sst_file_writer.Open(file6));
|
||||
ASSERT_OK(sst_file_writer.Put("a", "a2"));
|
||||
ExternalSstFileInfo file6_info;
|
||||
ASSERT_OK(sst_file_writer.Finish(&file6_info));
|
||||
files.push_back(std::move(file6));
|
||||
std::string file7 = sst_files_dir_ + "file7.sst";
|
||||
ASSERT_OK(sst_file_writer.Open(file7));
|
||||
ASSERT_OK(sst_file_writer.Put("x", "x2"));
|
||||
ExternalSstFileInfo file7_info;
|
||||
ASSERT_OK(sst_file_writer.Finish(&file7_info));
|
||||
files.push_back(std::move(file7));
|
||||
}
|
||||
|
||||
int num_compactions = 0;
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"VersionSet::MakeInputIterator:NewCompactionMergingIterator",
|
||||
[&](void* arg) {
|
||||
size_t* num_input_files = static_cast<size_t*>(arg);
|
||||
EXPECT_EQ(1, *num_input_files);
|
||||
num_compactions += 1;
|
||||
});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
ASSERT_OK(db_->IngestExternalFile(files, ifo));
|
||||
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
ASSERT_EQ(2, NumTableFilesAtLevel(4));
|
||||
ASSERT_EQ(0, NumTableFilesAtLevel(5));
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(6));
|
||||
ASSERT_EQ(2, num_compactions);
|
||||
|
||||
ASSERT_EQ(Get("a"), "a2");
|
||||
ASSERT_EQ(Get("h"), "h");
|
||||
ASSERT_EQ(Get("x"), "x2");
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
TEST_F(ExternalSSTFileBasicTest, StandaloneRangeDeletionEndKeyIsExclusive) {
|
||||
Options options = CurrentOptions();
|
||||
options.compaction_style = CompactionStyle::kCompactionStyleUniversal;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
std::vector<std::string> files;
|
||||
{
|
||||
SstFileWriter sst_file_writer(EnvOptions(), options);
|
||||
std::string file1 = sst_files_dir_ + "file1.sst";
|
||||
ASSERT_OK(sst_file_writer.Open(file1));
|
||||
ASSERT_OK(sst_file_writer.Put("a", "a"));
|
||||
ASSERT_OK(sst_file_writer.Put("b", "b"));
|
||||
ExternalSstFileInfo file1_info;
|
||||
ASSERT_OK(sst_file_writer.Finish(&file1_info));
|
||||
files.push_back(std::move(file1));
|
||||
}
|
||||
|
||||
IngestExternalFileOptions ifo;
|
||||
ASSERT_OK(db_->IngestExternalFile(files, ifo));
|
||||
ASSERT_EQ(Get("a"), "a");
|
||||
ASSERT_EQ(Get("b"), "b");
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(6));
|
||||
|
||||
{
|
||||
// A standalone range deletion with its exclusive end matching the range end
|
||||
// of file doesn't fully delete it.
|
||||
files.clear();
|
||||
SstFileWriter sst_file_writer(EnvOptions(), options);
|
||||
std::string file2 = sst_files_dir_ + "file2.sst";
|
||||
ASSERT_OK(sst_file_writer.Open(file2));
|
||||
ASSERT_OK(sst_file_writer.DeleteRange("a", "b"));
|
||||
ExternalSstFileInfo file2_info;
|
||||
ASSERT_OK(sst_file_writer.Finish(&file2_info));
|
||||
files.push_back(std::move(file2));
|
||||
}
|
||||
|
||||
bool compaction_iter_input_checked = false;
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"VersionSet::MakeInputIterator:NewCompactionMergingIterator",
|
||||
[&](void* arg) {
|
||||
size_t* num_input_files = static_cast<size_t*>(arg);
|
||||
// Standalone range deletion file for ["a", "b") + file with ["a", "b"].
|
||||
EXPECT_EQ(2, *num_input_files);
|
||||
compaction_iter_input_checked = true;
|
||||
});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
ASSERT_OK(db_->IngestExternalFile(files, ifo));
|
||||
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
ASSERT_EQ(0, NumTableFilesAtLevel(4));
|
||||
ASSERT_EQ(0, NumTableFilesAtLevel(5));
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(6));
|
||||
ASSERT_TRUE(compaction_iter_input_checked);
|
||||
|
||||
ASSERT_EQ(Get("a"), "NOT_FOUND");
|
||||
ASSERT_EQ(Get("b"), "b");
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
TEST_F(ExternalSSTFileBasicTest, IngestFileAfterDBPut) {
|
||||
@@ -2045,7 +2403,7 @@ TEST_F(ExternalSSTFileBasicTest, FailIfNotBottommostLevel) {
|
||||
ifo.fail_if_not_bottommost_level = true;
|
||||
ifo.snapshot_consistency = true;
|
||||
const Status s = db_->IngestExternalFile({file_path}, ifo);
|
||||
ASSERT_TRUE(s.IsTryAgain());
|
||||
ASSERT_TRUE(s.ok());
|
||||
}
|
||||
|
||||
// Test level compaction
|
||||
|
||||
@@ -67,7 +67,6 @@ Status ExternalSstFileIngestionJob::Prepare(
|
||||
files_to_ingest_.emplace_back(std::move(file_to_ingest));
|
||||
}
|
||||
|
||||
const Comparator* ucmp = cfd_->internal_comparator().user_comparator();
|
||||
auto num_files = files_to_ingest_.size();
|
||||
if (num_files == 0) {
|
||||
return Status::InvalidArgument("The list of files is empty");
|
||||
@@ -78,16 +77,12 @@ Status ExternalSstFileIngestionJob::Prepare(
|
||||
sorted_files.push_back(&files_to_ingest_[i]);
|
||||
}
|
||||
|
||||
std::sort(
|
||||
sorted_files.begin(), sorted_files.end(),
|
||||
[&ucmp](const IngestedFileInfo* info1, const IngestedFileInfo* info2) {
|
||||
return sstableKeyCompare(ucmp, info1->smallest_internal_key,
|
||||
info2->smallest_internal_key) < 0;
|
||||
});
|
||||
std::sort(sorted_files.begin(), sorted_files.end(), file_range_checker_);
|
||||
|
||||
for (size_t i = 0; i + 1 < num_files; i++) {
|
||||
if (sstableKeyCompare(ucmp, sorted_files[i]->largest_internal_key,
|
||||
sorted_files[i + 1]->smallest_internal_key) >= 0) {
|
||||
if (file_range_checker_.OverlapsWithPrev(sorted_files[i],
|
||||
sorted_files[i + 1],
|
||||
/* ranges_sorted= */ true)) {
|
||||
files_overlap_ = true;
|
||||
break;
|
||||
}
|
||||
@@ -100,7 +95,7 @@ Status ExternalSstFileIngestionJob::Prepare(
|
||||
"behind mode.");
|
||||
}
|
||||
|
||||
if (ucmp->timestamp_size() > 0 && files_overlap_) {
|
||||
if (ucmp_->timestamp_size() > 0 && files_overlap_) {
|
||||
return Status::NotSupported(
|
||||
"Files with overlapping ranges cannot be ingested to column "
|
||||
"family with user-defined timestamp enabled.");
|
||||
@@ -336,9 +331,35 @@ Status ExternalSstFileIngestionJob::Prepare(
|
||||
}
|
||||
}
|
||||
|
||||
if (status.ok()) {
|
||||
DivideInputFilesIntoBatches();
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
void ExternalSstFileIngestionJob::DivideInputFilesIntoBatches() {
|
||||
if (!files_overlap_) {
|
||||
// No overlap, treat as one batch without the need of tracking overall batch
|
||||
// range.
|
||||
file_batches_to_ingest_.emplace_back(/* _track_batch_range= */ false);
|
||||
for (auto& file : files_to_ingest_) {
|
||||
file_batches_to_ingest_.back().AddFile(&file, file_range_checker_);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
file_batches_to_ingest_.emplace_back(/* _track_batch_range= */ true);
|
||||
for (auto& file : files_to_ingest_) {
|
||||
if (file_range_checker_.OverlapsWithPrev(&file_batches_to_ingest_.back(),
|
||||
&file,
|
||||
/* ranges_sorted= */ false)) {
|
||||
file_batches_to_ingest_.emplace_back(/* _track_batch_range= */ true);
|
||||
}
|
||||
file_batches_to_ingest_.back().AddFile(&file, file_range_checker_);
|
||||
}
|
||||
}
|
||||
|
||||
Status ExternalSstFileIngestionJob::NeedsFlush(bool* flush_needed,
|
||||
SuperVersion* super_version) {
|
||||
size_t n = files_to_ingest_.size();
|
||||
@@ -353,9 +374,7 @@ Status ExternalSstFileIngestionJob::NeedsFlush(bool* flush_needed,
|
||||
if (!ingestion_options_.allow_blocking_flush) {
|
||||
status = Status::InvalidArgument("External file requires flush");
|
||||
}
|
||||
auto ucmp = cfd_->user_comparator();
|
||||
assert(ucmp);
|
||||
if (ucmp->timestamp_size() > 0) {
|
||||
if (ucmp_->timestamp_size() > 0) {
|
||||
status = Status::InvalidArgument(
|
||||
"Column family enables user-defined timestamps, please make "
|
||||
"sure the key range (without timestamp) of external file does not "
|
||||
@@ -397,14 +416,39 @@ Status ExternalSstFileIngestionJob::Run() {
|
||||
edit_.SetColumnFamily(cfd_->GetID());
|
||||
// The levels that the files will be ingested into
|
||||
|
||||
for (IngestedFileInfo& f : files_to_ingest_) {
|
||||
std::optional<int> prev_batch_uppermost_level;
|
||||
for (auto& batch : file_batches_to_ingest_) {
|
||||
int batch_uppermost_level = 0;
|
||||
status = AssignLevelsForOneBatch(batch, super_version, force_global_seqno,
|
||||
&last_seqno, &batch_uppermost_level,
|
||||
prev_batch_uppermost_level);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
prev_batch_uppermost_level = batch_uppermost_level;
|
||||
}
|
||||
|
||||
CreateEquivalentFileIngestingCompactions();
|
||||
return status;
|
||||
}
|
||||
|
||||
Status ExternalSstFileIngestionJob::AssignLevelsForOneBatch(
|
||||
FileBatchInfo& batch, SuperVersion* super_version, bool force_global_seqno,
|
||||
SequenceNumber* last_seqno, int* batch_uppermost_level,
|
||||
std::optional<int> prev_batch_uppermost_level) {
|
||||
Status status;
|
||||
assert(batch_uppermost_level);
|
||||
*batch_uppermost_level = std::numeric_limits<int>::max();
|
||||
for (IngestedFileInfo* file : batch.files) {
|
||||
assert(file);
|
||||
SequenceNumber assigned_seqno = 0;
|
||||
if (ingestion_options_.ingest_behind) {
|
||||
status = CheckLevelForIngestedBehindFile(&f);
|
||||
status = CheckLevelForIngestedBehindFile(file);
|
||||
} else {
|
||||
status = AssignLevelAndSeqnoForIngestedFile(
|
||||
super_version, force_global_seqno, cfd_->ioptions()->compaction_style,
|
||||
last_seqno, &f, &assigned_seqno);
|
||||
*last_seqno, file, &assigned_seqno, prev_batch_uppermost_level);
|
||||
}
|
||||
|
||||
// Modify the smallest/largest internal key to include the sequence number
|
||||
@@ -413,38 +457,38 @@ Status ExternalSstFileIngestionJob::Run() {
|
||||
// exclusive endpoint.
|
||||
ParsedInternalKey smallest_parsed, largest_parsed;
|
||||
if (status.ok()) {
|
||||
status = ParseInternalKey(*f.smallest_internal_key.rep(),
|
||||
status = ParseInternalKey(*(file->smallest_internal_key.rep()),
|
||||
&smallest_parsed, false /* log_err_key */);
|
||||
}
|
||||
if (status.ok()) {
|
||||
status = ParseInternalKey(*f.largest_internal_key.rep(), &largest_parsed,
|
||||
false /* log_err_key */);
|
||||
status = ParseInternalKey(*(file->largest_internal_key.rep()),
|
||||
&largest_parsed, false /* log_err_key */);
|
||||
}
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
if (smallest_parsed.sequence == 0 && assigned_seqno != 0) {
|
||||
UpdateInternalKey(f.smallest_internal_key.rep(), assigned_seqno,
|
||||
UpdateInternalKey(file->smallest_internal_key.rep(), assigned_seqno,
|
||||
smallest_parsed.type);
|
||||
}
|
||||
if (largest_parsed.sequence == 0 && assigned_seqno != 0) {
|
||||
UpdateInternalKey(f.largest_internal_key.rep(), assigned_seqno,
|
||||
UpdateInternalKey(file->largest_internal_key.rep(), assigned_seqno,
|
||||
largest_parsed.type);
|
||||
}
|
||||
|
||||
status = AssignGlobalSeqnoForIngestedFile(&f, assigned_seqno);
|
||||
status = AssignGlobalSeqnoForIngestedFile(file, assigned_seqno);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
TEST_SYNC_POINT_CALLBACK("ExternalSstFileIngestionJob::Run",
|
||||
&assigned_seqno);
|
||||
assert(assigned_seqno == 0 || assigned_seqno == last_seqno + 1);
|
||||
if (assigned_seqno > last_seqno) {
|
||||
last_seqno = assigned_seqno;
|
||||
assert(assigned_seqno == 0 || assigned_seqno == *last_seqno + 1);
|
||||
if (assigned_seqno > *last_seqno) {
|
||||
*last_seqno = assigned_seqno;
|
||||
++consumed_seqno_count_;
|
||||
}
|
||||
|
||||
status = GenerateChecksumForIngestedFile(&f);
|
||||
status = GenerateChecksumForIngestedFile(file);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
@@ -459,31 +503,40 @@ Status ExternalSstFileIngestionJob::Run() {
|
||||
static_cast<uint64_t>(temp_current_time);
|
||||
}
|
||||
uint64_t tail_size = 0;
|
||||
bool contain_no_data_blocks = f.table_properties.num_entries > 0 &&
|
||||
(f.table_properties.num_entries ==
|
||||
f.table_properties.num_range_deletions);
|
||||
if (f.table_properties.tail_start_offset > 0 || contain_no_data_blocks) {
|
||||
uint64_t file_size = f.fd.GetFileSize();
|
||||
assert(f.table_properties.tail_start_offset <= file_size);
|
||||
tail_size = file_size - f.table_properties.tail_start_offset;
|
||||
bool contain_no_data_blocks = file->table_properties.num_entries > 0 &&
|
||||
(file->table_properties.num_entries ==
|
||||
file->table_properties.num_range_deletions);
|
||||
if (file->table_properties.tail_start_offset > 0 ||
|
||||
contain_no_data_blocks) {
|
||||
uint64_t file_size = file->fd.GetFileSize();
|
||||
assert(file->table_properties.tail_start_offset <= file_size);
|
||||
tail_size = file_size - file->table_properties.tail_start_offset;
|
||||
}
|
||||
|
||||
bool marked_for_compaction =
|
||||
file->table_properties.num_range_deletions == 1 &&
|
||||
(file->table_properties.num_entries ==
|
||||
file->table_properties.num_range_deletions);
|
||||
FileMetaData f_metadata(
|
||||
f.fd.GetNumber(), f.fd.GetPathId(), f.fd.GetFileSize(),
|
||||
f.smallest_internal_key, f.largest_internal_key, f.assigned_seqno,
|
||||
f.assigned_seqno, false, f.file_temperature, kInvalidBlobFileNumber,
|
||||
oldest_ancester_time, current_time,
|
||||
file->fd.GetNumber(), file->fd.GetPathId(), file->fd.GetFileSize(),
|
||||
file->smallest_internal_key, file->largest_internal_key,
|
||||
file->assigned_seqno, file->assigned_seqno, false,
|
||||
file->file_temperature, kInvalidBlobFileNumber, oldest_ancester_time,
|
||||
current_time,
|
||||
ingestion_options_.ingest_behind
|
||||
? kReservedEpochNumberForFileIngestedBehind
|
||||
: cfd_->NewEpochNumber(),
|
||||
f.file_checksum, f.file_checksum_func_name, f.unique_id, 0, tail_size,
|
||||
f.user_defined_timestamps_persisted);
|
||||
f_metadata.temperature = f.file_temperature;
|
||||
edit_.AddFile(f.picked_level, f_metadata);
|
||||
file->file_checksum, file->file_checksum_func_name, file->unique_id, 0,
|
||||
tail_size, file->user_defined_timestamps_persisted);
|
||||
f_metadata.temperature = file->file_temperature;
|
||||
f_metadata.marked_for_compaction = marked_for_compaction;
|
||||
edit_.AddFile(file->picked_level, f_metadata);
|
||||
|
||||
*batch_uppermost_level =
|
||||
std::min(*batch_uppermost_level, file->picked_level);
|
||||
}
|
||||
|
||||
CreateEquivalentFileIngestingCompactions();
|
||||
return status;
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
void ExternalSstFileIngestionJob::CreateEquivalentFileIngestingCompactions() {
|
||||
@@ -519,20 +572,17 @@ void ExternalSstFileIngestionJob::CreateEquivalentFileIngestingCompactions() {
|
||||
file_ingesting_compactions_.push_back(new Compaction(
|
||||
cfd_->current()->storage_info(), *cfd_->ioptions(), mutable_cf_options,
|
||||
mutable_db_options_, {input}, output_level,
|
||||
MaxFileSizeForLevel(
|
||||
mutable_cf_options, output_level,
|
||||
cfd_->ioptions()->compaction_style) /* output file size
|
||||
limit,
|
||||
* not applicable
|
||||
*/
|
||||
,
|
||||
/* output file size limit not applicable */
|
||||
MaxFileSizeForLevel(mutable_cf_options, output_level,
|
||||
cfd_->ioptions()->compaction_style),
|
||||
LLONG_MAX /* max compaction bytes, not applicable */,
|
||||
0 /* output path ID, not applicable */, mutable_cf_options.compression,
|
||||
mutable_cf_options.compression_opts,
|
||||
mutable_cf_options.default_write_temperature,
|
||||
0 /* max_subcompaction, not applicable */,
|
||||
{} /* grandparents, not applicable */, false /* is manual */,
|
||||
"" /* trim_ts */, -1 /* score, not applicable */,
|
||||
{} /* grandparents, not applicable */,
|
||||
std::nullopt /* earliest_snapshot */, nullptr /* snapshot_checker */,
|
||||
false /* is manual */, "" /* trim_ts */, -1 /* score, not applicable */,
|
||||
false /* is deletion compaction, not applicable */,
|
||||
files_overlap_ /* l0_files_might_overlap, not applicable */,
|
||||
CompactionReason::kExternalSstIngestion));
|
||||
@@ -679,7 +729,10 @@ Status ExternalSstFileIngestionJob::ResetTableReader(
|
||||
new RandomAccessFileReader(std::move(sst_file), external_file,
|
||||
nullptr /*Env*/, io_tracer_));
|
||||
table_reader->reset();
|
||||
status = cfd_->ioptions()->table_factory->NewTableReader(
|
||||
ReadOptions ro;
|
||||
ro.fill_cache = ingestion_options_.fill_cache;
|
||||
status = sv->mutable_cf_options.table_factory->NewTableReader(
|
||||
ro,
|
||||
TableReaderOptions(
|
||||
*cfd_->ioptions(), sv->mutable_cf_options.prefix_extractor,
|
||||
env_options_, cfd_->internal_comparator(),
|
||||
@@ -691,7 +744,9 @@ Status ExternalSstFileIngestionJob::ResetTableReader(
|
||||
/*cur_file_num*/ new_file_number,
|
||||
/* unique_id */ {}, /* largest_seqno */ 0,
|
||||
/* tail_size */ 0, user_defined_timestamps_persisted),
|
||||
std::move(sst_file_reader), file_to_ingest->file_size, table_reader);
|
||||
std::move(sst_file_reader), file_to_ingest->file_size, table_reader,
|
||||
// No need to prefetch index/filter if caching is not needed.
|
||||
/*prefetch_index_and_filter_in_cache=*/ingestion_options_.fill_cache);
|
||||
return status;
|
||||
}
|
||||
|
||||
@@ -707,6 +762,7 @@ Status ExternalSstFileIngestionJob::SanityCheckTableProperties(
|
||||
// Get table version
|
||||
auto version_iter = uprops.find(ExternalSstFilePropertyNames::kVersion);
|
||||
if (version_iter == uprops.end()) {
|
||||
assert(!SstFileWriter::CreatedBySstFileWriter(*props));
|
||||
if (!ingestion_options_.allow_db_generated_files) {
|
||||
return Status::Corruption("External file version not found");
|
||||
} else {
|
||||
@@ -715,6 +771,7 @@ Status ExternalSstFileIngestionJob::SanityCheckTableProperties(
|
||||
file_to_ingest->version = 0;
|
||||
}
|
||||
} else {
|
||||
assert(SstFileWriter::CreatedBySstFileWriter(*props));
|
||||
file_to_ingest->version = DecodeFixed32(version_iter->second.c_str());
|
||||
}
|
||||
|
||||
@@ -787,9 +844,7 @@ Status ExternalSstFileIngestionJob::SanityCheckTableProperties(
|
||||
// `TableReader` is initialized with `user_defined_timestamps_persisted` flag
|
||||
// to be true. If its value changed to false after this sanity check, we
|
||||
// need to reset the `TableReader`.
|
||||
auto ucmp = cfd_->user_comparator();
|
||||
assert(ucmp);
|
||||
if (ucmp->timestamp_size() > 0 &&
|
||||
if (ucmp_->timestamp_size() > 0 &&
|
||||
!file_to_ingest->user_defined_timestamps_persisted) {
|
||||
s = ResetTableReader(external_file, new_file_number,
|
||||
file_to_ingest->user_defined_timestamps_persisted, sv,
|
||||
@@ -839,6 +894,7 @@ Status ExternalSstFileIngestionJob::GetIngestedFileInfo(
|
||||
// TODO: plumb Env::IOActivity, Env::IOPriority
|
||||
ReadOptions ro;
|
||||
ro.readahead_size = ingestion_options_.verify_checksums_readahead_size;
|
||||
ro.fill_cache = ingestion_options_.fill_cache;
|
||||
status = table_reader->VerifyChecksum(
|
||||
ro, TableReaderCaller::kExternalSSTIngestion);
|
||||
if (!status.ok()) {
|
||||
@@ -849,16 +905,12 @@ Status ExternalSstFileIngestionJob::GetIngestedFileInfo(
|
||||
ParsedInternalKey key;
|
||||
// TODO: plumb Env::IOActivity, Env::IOPriority
|
||||
ReadOptions ro;
|
||||
ro.fill_cache = ingestion_options_.fill_cache;
|
||||
std::unique_ptr<InternalIterator> iter(table_reader->NewIterator(
|
||||
ro, sv->mutable_cf_options.prefix_extractor.get(), /*arena=*/nullptr,
|
||||
/*skip_filters=*/false, TableReaderCaller::kExternalSSTIngestion));
|
||||
|
||||
// Get first (smallest) and last (largest) key from file.
|
||||
file_to_ingest->smallest_internal_key =
|
||||
InternalKey("", 0, ValueType::kTypeValue);
|
||||
file_to_ingest->largest_internal_key =
|
||||
InternalKey("", 0, ValueType::kTypeValue);
|
||||
bool bounds_set = false;
|
||||
bool allow_data_in_errors = db_options_.allow_data_in_errors;
|
||||
iter->SeekToFirst();
|
||||
if (iter->Valid()) {
|
||||
@@ -874,7 +926,8 @@ Status ExternalSstFileIngestionJob::GetIngestedFileInfo(
|
||||
file_to_ingest->smallest_internal_key.SetFrom(key);
|
||||
|
||||
Slice largest;
|
||||
if (strcmp(cfd_->ioptions()->table_factory->Name(), "PlainTable") == 0) {
|
||||
if (strcmp(sv->mutable_cf_options.table_factory->Name(), "PlainTable") ==
|
||||
0) {
|
||||
// PlainTable iterator does not support SeekToLast().
|
||||
largest = iter->key();
|
||||
for (; iter->Valid(); iter->Next()) {
|
||||
@@ -908,8 +961,6 @@ Status ExternalSstFileIngestionJob::GetIngestedFileInfo(
|
||||
return Status::Corruption("External file has non zero sequence number");
|
||||
}
|
||||
file_to_ingest->largest_internal_key.SetFrom(key);
|
||||
|
||||
bounds_set = true;
|
||||
} else if (!iter->status().ok()) {
|
||||
return iter->status();
|
||||
}
|
||||
@@ -946,7 +997,6 @@ Status ExternalSstFileIngestionJob::GetIngestedFileInfo(
|
||||
table_reader->NewRangeTombstoneIterator(ro));
|
||||
// We may need to adjust these key bounds, depending on whether any range
|
||||
// deletion tombstones extend past them.
|
||||
const Comparator* ucmp = cfd_->user_comparator();
|
||||
if (range_del_iter != nullptr) {
|
||||
for (range_del_iter->SeekToFirst(); range_del_iter->Valid();
|
||||
range_del_iter->Next()) {
|
||||
@@ -962,24 +1012,13 @@ Status ExternalSstFileIngestionJob::GetIngestedFileInfo(
|
||||
"number.");
|
||||
}
|
||||
RangeTombstone tombstone(key, range_del_iter->value());
|
||||
|
||||
InternalKey start_key = tombstone.SerializeKey();
|
||||
if (!bounds_set ||
|
||||
sstableKeyCompare(ucmp, start_key,
|
||||
file_to_ingest->smallest_internal_key) < 0) {
|
||||
file_to_ingest->smallest_internal_key = start_key;
|
||||
}
|
||||
InternalKey end_key = tombstone.SerializeEndKey();
|
||||
if (!bounds_set ||
|
||||
sstableKeyCompare(ucmp, end_key,
|
||||
file_to_ingest->largest_internal_key) > 0) {
|
||||
file_to_ingest->largest_internal_key = end_key;
|
||||
}
|
||||
bounds_set = true;
|
||||
file_range_checker_.MaybeUpdateRange(tombstone.SerializeKey(),
|
||||
tombstone.SerializeEndKey(),
|
||||
file_to_ingest);
|
||||
}
|
||||
}
|
||||
|
||||
const size_t ts_sz = ucmp->timestamp_size();
|
||||
const size_t ts_sz = ucmp_->timestamp_size();
|
||||
Slice smallest = file_to_ingest->smallest_internal_key.user_key();
|
||||
Slice largest = file_to_ingest->largest_internal_key.user_key();
|
||||
if (ts_sz > 0) {
|
||||
@@ -1008,16 +1047,19 @@ Status ExternalSstFileIngestionJob::GetIngestedFileInfo(
|
||||
Status ExternalSstFileIngestionJob::AssignLevelAndSeqnoForIngestedFile(
|
||||
SuperVersion* sv, bool force_global_seqno, CompactionStyle compaction_style,
|
||||
SequenceNumber last_seqno, IngestedFileInfo* file_to_ingest,
|
||||
SequenceNumber* assigned_seqno) {
|
||||
SequenceNumber* assigned_seqno,
|
||||
std::optional<int> prev_batch_uppermost_level) {
|
||||
Status status;
|
||||
*assigned_seqno = 0;
|
||||
auto ucmp = cfd_->user_comparator();
|
||||
const size_t ts_sz = ucmp->timestamp_size();
|
||||
const size_t ts_sz = ucmp_->timestamp_size();
|
||||
assert(!prev_batch_uppermost_level.has_value() ||
|
||||
prev_batch_uppermost_level.value() < cfd_->NumberLevels());
|
||||
bool must_assign_to_l0 = prev_batch_uppermost_level.has_value() &&
|
||||
prev_batch_uppermost_level.value() == 0;
|
||||
if (force_global_seqno || files_overlap_ ||
|
||||
compaction_style == kCompactionStyleFIFO) {
|
||||
compaction_style == kCompactionStyleFIFO || must_assign_to_l0) {
|
||||
*assigned_seqno = last_seqno + 1;
|
||||
// If files overlap, we have to ingest them at level 0.
|
||||
if (files_overlap_ || compaction_style == kCompactionStyleFIFO) {
|
||||
if (compaction_style == kCompactionStyleFIFO || must_assign_to_l0) {
|
||||
assert(ts_sz == 0);
|
||||
file_to_ingest->picked_level = 0;
|
||||
if (ingestion_options_.fail_if_not_bottommost_level &&
|
||||
@@ -1034,11 +1076,16 @@ Status ExternalSstFileIngestionJob::AssignLevelAndSeqnoForIngestedFile(
|
||||
Arena arena;
|
||||
// TODO: plumb Env::IOActivity, Env::IOPriority
|
||||
ReadOptions ro;
|
||||
ro.fill_cache = ingestion_options_.fill_cache;
|
||||
ro.total_order_seek = true;
|
||||
int target_level = 0;
|
||||
auto* vstorage = cfd_->current()->storage_info();
|
||||
assert(!must_assign_to_l0);
|
||||
int exclusive_end_level = prev_batch_uppermost_level.has_value()
|
||||
? prev_batch_uppermost_level.value()
|
||||
: cfd_->NumberLevels();
|
||||
|
||||
for (int lvl = 0; lvl < cfd_->NumberLevels(); lvl++) {
|
||||
for (int lvl = 0; lvl < exclusive_end_level; lvl++) {
|
||||
if (lvl > 0 && lvl < vstorage->base_level()) {
|
||||
continue;
|
||||
}
|
||||
@@ -1065,8 +1112,6 @@ Status ExternalSstFileIngestionJob::AssignLevelAndSeqnoForIngestedFile(
|
||||
overlap_with_db = true;
|
||||
break;
|
||||
}
|
||||
} else if (compaction_style == kCompactionStyleUniversal) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// We don't overlap with any keys in this level, but we still need to check
|
||||
|
||||
@@ -25,13 +25,74 @@ namespace ROCKSDB_NAMESPACE {
|
||||
class Directories;
|
||||
class SystemClock;
|
||||
|
||||
struct IngestedFileInfo {
|
||||
struct KeyRangeInfo {
|
||||
// Smallest internal key in an external file or for a batch of external files.
|
||||
InternalKey smallest_internal_key;
|
||||
// Largest internal key in an external file or for a batch of external files.
|
||||
InternalKey largest_internal_key;
|
||||
|
||||
bool empty() const {
|
||||
return smallest_internal_key.size() == 0 &&
|
||||
largest_internal_key.size() == 0;
|
||||
}
|
||||
};
|
||||
|
||||
// Helper class to apply SST file key range checks to the external files.
|
||||
class ExternalFileRangeChecker {
|
||||
public:
|
||||
explicit ExternalFileRangeChecker(const Comparator* ucmp) : ucmp_(ucmp) {}
|
||||
|
||||
// Operator used for sorting ranges.
|
||||
bool operator()(const KeyRangeInfo* prev_range,
|
||||
const KeyRangeInfo* range) const {
|
||||
assert(prev_range);
|
||||
assert(range);
|
||||
return sstableKeyCompare(ucmp_, prev_range->smallest_internal_key,
|
||||
range->smallest_internal_key) < 0;
|
||||
}
|
||||
|
||||
// Check whether `range` overlaps with `prev_range`. `ranges_sorted` can be
|
||||
// set to true when the inputs are already sorted based on the sorting logic
|
||||
// provided by this checker's operator(), which can help simplify the check.
|
||||
bool OverlapsWithPrev(const KeyRangeInfo* prev_range,
|
||||
const KeyRangeInfo* range,
|
||||
bool ranges_sorted = false) const {
|
||||
assert(prev_range);
|
||||
assert(range);
|
||||
if (prev_range->empty() || range->empty()) {
|
||||
return false;
|
||||
}
|
||||
if (ranges_sorted) {
|
||||
return sstableKeyCompare(ucmp_, prev_range->largest_internal_key,
|
||||
range->smallest_internal_key) >= 0;
|
||||
}
|
||||
|
||||
return sstableKeyCompare(ucmp_, prev_range->largest_internal_key,
|
||||
range->smallest_internal_key) >= 0 &&
|
||||
sstableKeyCompare(ucmp_, prev_range->smallest_internal_key,
|
||||
range->largest_internal_key) <= 0;
|
||||
}
|
||||
|
||||
void MaybeUpdateRange(const InternalKey& start_key,
|
||||
const InternalKey& end_key, KeyRangeInfo* range) const {
|
||||
assert(range);
|
||||
if (range->smallest_internal_key.size() == 0 ||
|
||||
sstableKeyCompare(ucmp_, start_key, range->smallest_internal_key) < 0) {
|
||||
range->smallest_internal_key = start_key;
|
||||
}
|
||||
if (range->largest_internal_key.size() == 0 ||
|
||||
sstableKeyCompare(ucmp_, end_key, range->largest_internal_key) > 0) {
|
||||
range->largest_internal_key = end_key;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
const Comparator* ucmp_;
|
||||
};
|
||||
|
||||
struct IngestedFileInfo : public KeyRangeInfo {
|
||||
// External file path
|
||||
std::string external_file_path;
|
||||
// Smallest internal key in external file
|
||||
InternalKey smallest_internal_key;
|
||||
// Largest internal key in external file
|
||||
InternalKey largest_internal_key;
|
||||
// NOTE: use below two fields for all `*Overlap*` types of checks instead of
|
||||
// smallest_internal_key.user_key() and largest_internal_key.user_key().
|
||||
// The smallest / largest user key contained in the file for key range checks.
|
||||
@@ -94,6 +155,30 @@ struct IngestedFileInfo {
|
||||
bool user_defined_timestamps_persisted = true;
|
||||
};
|
||||
|
||||
// A batch of files.
|
||||
struct FileBatchInfo : public KeyRangeInfo {
|
||||
autovector<IngestedFileInfo*> files;
|
||||
// When true, `smallest_internal_key` and `largest_internal_key` will be
|
||||
// tracked and updated as new file get added via `AddFile`. When false, we
|
||||
// bypass this tracking. This is used when the all input external files
|
||||
// are already checked and not overlapping, and they just need to be added
|
||||
// into one default batch.
|
||||
bool track_batch_range;
|
||||
|
||||
void AddFile(IngestedFileInfo* file,
|
||||
const ExternalFileRangeChecker& key_range_checker) {
|
||||
assert(file);
|
||||
files.push_back(file);
|
||||
if (track_batch_range) {
|
||||
key_range_checker.MaybeUpdateRange(file->smallest_internal_key,
|
||||
file->largest_internal_key, this);
|
||||
}
|
||||
}
|
||||
|
||||
explicit FileBatchInfo(bool _track_batch_range)
|
||||
: track_batch_range(_track_batch_range) {}
|
||||
};
|
||||
|
||||
class ExternalSstFileIngestionJob {
|
||||
public:
|
||||
ExternalSstFileIngestionJob(
|
||||
@@ -108,6 +193,8 @@ class ExternalSstFileIngestionJob {
|
||||
fs_(db_options.fs, io_tracer),
|
||||
versions_(versions),
|
||||
cfd_(cfd),
|
||||
ucmp_(cfd ? cfd->user_comparator() : nullptr),
|
||||
file_range_checker_(ucmp_),
|
||||
db_options_(db_options),
|
||||
mutable_db_options_(mutable_db_options),
|
||||
env_options_(env_options),
|
||||
@@ -119,6 +206,8 @@ class ExternalSstFileIngestionJob {
|
||||
consumed_seqno_count_(0),
|
||||
io_tracer_(io_tracer) {
|
||||
assert(directories != nullptr);
|
||||
assert(cfd_);
|
||||
assert(ucmp_);
|
||||
}
|
||||
|
||||
~ExternalSstFileIngestionJob() { UnregisterRange(); }
|
||||
@@ -194,15 +283,38 @@ class ExternalSstFileIngestionJob {
|
||||
IngestedFileInfo* file_to_ingest,
|
||||
SuperVersion* sv);
|
||||
|
||||
// If the input files' key range overlaps themselves, this function divides
|
||||
// them in the user specified order into multiple batches. Where the files
|
||||
// within a batch do not overlap with each other, but key range could overlap
|
||||
// between batches.
|
||||
// If the input files' key range don't overlap themselves, they always just
|
||||
// make one batch.
|
||||
void DivideInputFilesIntoBatches();
|
||||
|
||||
// Assign level for the files in one batch. The files within one batch are not
|
||||
// overlapping, and we assign level to each file one after another.
|
||||
// If `prev_batch_uppermost_level` is specified, all files in this batch will
|
||||
// be assigned to levels that are higher than `prev_batch_uppermost_level`.
|
||||
// The uppermost level used by this batch of files is tracked too, so that it
|
||||
// can be used by the next batch.
|
||||
// REQUIRES: Mutex held
|
||||
Status AssignLevelsForOneBatch(FileBatchInfo& batch,
|
||||
SuperVersion* super_version,
|
||||
bool force_global_seqno,
|
||||
SequenceNumber* last_seqno,
|
||||
int* batch_uppermost_level,
|
||||
std::optional<int> prev_batch_uppermost_level);
|
||||
|
||||
// Assign `file_to_ingest` the appropriate sequence number and the lowest
|
||||
// possible level that it can be ingested to according to compaction_style.
|
||||
// If `prev_batch_uppermost_level` is specified, the file will only be
|
||||
// assigned to levels tha are higher than `prev_batch_uppermost_level`.
|
||||
// REQUIRES: Mutex held
|
||||
Status AssignLevelAndSeqnoForIngestedFile(SuperVersion* sv,
|
||||
bool force_global_seqno,
|
||||
CompactionStyle compaction_style,
|
||||
SequenceNumber last_seqno,
|
||||
IngestedFileInfo* file_to_ingest,
|
||||
SequenceNumber* assigned_seqno);
|
||||
Status AssignLevelAndSeqnoForIngestedFile(
|
||||
SuperVersion* sv, bool force_global_seqno,
|
||||
CompactionStyle compaction_style, SequenceNumber last_seqno,
|
||||
IngestedFileInfo* file_to_ingest, SequenceNumber* assigned_seqno,
|
||||
std::optional<int> prev_batch_uppermost_level);
|
||||
|
||||
// File that we want to ingest behind always goes to the lowest level;
|
||||
// we just check that it fits in the level, that DB allows ingest_behind,
|
||||
@@ -237,11 +349,14 @@ class ExternalSstFileIngestionJob {
|
||||
FileSystemPtr fs_;
|
||||
VersionSet* versions_;
|
||||
ColumnFamilyData* cfd_;
|
||||
const Comparator* ucmp_;
|
||||
ExternalFileRangeChecker file_range_checker_;
|
||||
const ImmutableDBOptions& db_options_;
|
||||
const MutableDBOptions& mutable_db_options_;
|
||||
const EnvOptions& env_options_;
|
||||
SnapshotList* db_snapshots_;
|
||||
autovector<IngestedFileInfo> files_to_ingest_;
|
||||
std::vector<FileBatchInfo> file_batches_to_ingest_;
|
||||
const IngestExternalFileOptions& ingestion_options_;
|
||||
Directories* directories_;
|
||||
EventLogger* event_logger_;
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#include <table/block_based/block_based_table_factory.h>
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
@@ -150,7 +152,7 @@ class ExternalSSTFileTest
|
||||
bool verify_checksums_before_ingest = true, bool ingest_behind = false,
|
||||
bool sort_data = false,
|
||||
std::map<std::string, std::string>* true_data = nullptr,
|
||||
ColumnFamilyHandle* cfh = nullptr) {
|
||||
ColumnFamilyHandle* cfh = nullptr, bool fill_cache = false) {
|
||||
// Generate a file id if not provided
|
||||
if (file_id == -1) {
|
||||
file_id = last_file_id_ + 1;
|
||||
@@ -194,6 +196,7 @@ class ExternalSSTFileTest
|
||||
ifo.write_global_seqno = allow_global_seqno ? write_global_seqno : false;
|
||||
ifo.verify_checksums_before_ingest = verify_checksums_before_ingest;
|
||||
ifo.ingest_behind = ingest_behind;
|
||||
ifo.fill_cache = fill_cache;
|
||||
if (cfh) {
|
||||
s = db_->IngestExternalFile(cfh, {file_path}, ifo);
|
||||
} else {
|
||||
@@ -267,15 +270,15 @@ class ExternalSSTFileTest
|
||||
bool verify_checksums_before_ingest = true, bool ingest_behind = false,
|
||||
bool sort_data = false,
|
||||
std::map<std::string, std::string>* true_data = nullptr,
|
||||
ColumnFamilyHandle* cfh = nullptr) {
|
||||
ColumnFamilyHandle* cfh = nullptr, bool fill_cache = false) {
|
||||
std::vector<std::pair<std::string, std::string>> file_data;
|
||||
for (auto& k : keys) {
|
||||
file_data.emplace_back(Key(k), Key(k) + std::to_string(file_id));
|
||||
}
|
||||
return GenerateAndAddExternalFile(options, file_data, file_id,
|
||||
allow_global_seqno, write_global_seqno,
|
||||
verify_checksums_before_ingest,
|
||||
ingest_behind, sort_data, true_data, cfh);
|
||||
return GenerateAndAddExternalFile(
|
||||
options, file_data, file_id, allow_global_seqno, write_global_seqno,
|
||||
verify_checksums_before_ingest, ingest_behind, sort_data, true_data,
|
||||
cfh, fill_cache);
|
||||
}
|
||||
|
||||
Status DeprecatedAddFile(const std::vector<std::string>& files,
|
||||
@@ -314,6 +317,49 @@ TEST_F(ExternalSSTFileTest, ComparatorMismatch) {
|
||||
ASSERT_NOK(DeprecatedAddFile({file}));
|
||||
}
|
||||
|
||||
TEST_F(ExternalSSTFileTest, NoBlockCache) {
|
||||
LRUCacheOptions co;
|
||||
co.capacity = 32 << 20;
|
||||
std::shared_ptr<Cache> cache = NewLRUCache(co);
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.block_cache = cache;
|
||||
table_options.filter_policy.reset(NewBloomFilterPolicy(10));
|
||||
table_options.cache_index_and_filter_blocks = true;
|
||||
Options options = CurrentOptions();
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
Reopen(options);
|
||||
|
||||
size_t usage_before_ingestion = cache->GetUsage();
|
||||
std::map<std::string, std::string> true_data;
|
||||
// Ingest with fill_cache = true
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {1, 2}, -1, false, false, true,
|
||||
false, false, &true_data, nullptr,
|
||||
/*fill_cache=*/true));
|
||||
ASSERT_EQ(FilesPerLevel(), "0,0,0,0,0,0,1");
|
||||
EXPECT_GT(cache->GetUsage(), usage_before_ingestion);
|
||||
|
||||
TablePropertiesCollection tp;
|
||||
ASSERT_OK(db_->GetPropertiesOfAllTables(&tp));
|
||||
for (const auto& entry : tp) {
|
||||
EXPECT_GT(entry.second->index_size, 0);
|
||||
EXPECT_GT(entry.second->filter_size, 0);
|
||||
}
|
||||
|
||||
usage_before_ingestion = cache->GetUsage();
|
||||
// Ingest with fill_cache = false
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {3, 4}, -1, false, false, true,
|
||||
false, false, &true_data, nullptr,
|
||||
/*fill_cache=*/false));
|
||||
EXPECT_EQ(usage_before_ingestion, cache->GetUsage());
|
||||
|
||||
tp.clear();
|
||||
ASSERT_OK(db_->GetPropertiesOfAllTables(&tp));
|
||||
for (const auto& entry : tp) {
|
||||
EXPECT_GT(entry.second->index_size, 0);
|
||||
EXPECT_GT(entry.second->filter_size, 0);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ExternalSSTFileTest, Basic) {
|
||||
do {
|
||||
Options options = CurrentOptions();
|
||||
@@ -1941,9 +1987,9 @@ TEST_P(ExternalSSTFileTest, IngestFileWithGlobalSeqnoAssignedUniversal) {
|
||||
options, file_data, -1, true, write_global_seqno,
|
||||
verify_checksums_before_ingest, false, false, &true_data));
|
||||
|
||||
// This file overlap with files in L4, we will ingest it into the last
|
||||
// non-overlapping and non-empty level, in this case, it's L0.
|
||||
ASSERT_EQ("3,0,0,0,3", FilesPerLevel());
|
||||
// This file overlap with files in L4, we will ingest it into the closest
|
||||
// non-overlapping level, in this case, it's L3.
|
||||
ASSERT_EQ("2,0,0,1,3", FilesPerLevel());
|
||||
|
||||
size_t kcnt = 0;
|
||||
VerifyDBFromMap(true_data, &kcnt, false);
|
||||
|
||||
+21
-4
@@ -858,6 +858,12 @@ Status FlushJob::WriteLevel0Table() {
|
||||
meta_.temperature = mutable_cf_options_.default_write_temperature;
|
||||
file_options_.temperature = meta_.temperature;
|
||||
|
||||
const auto* ucmp = cfd_->internal_comparator().user_comparator();
|
||||
assert(ucmp);
|
||||
const size_t ts_sz = ucmp->timestamp_size();
|
||||
const bool logical_strip_timestamp =
|
||||
ts_sz > 0 && !cfd_->ioptions()->persist_user_defined_timestamps;
|
||||
|
||||
std::vector<BlobFileAddition> blob_file_additions;
|
||||
|
||||
{
|
||||
@@ -893,10 +899,21 @@ 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, /*prefix_extractor=*/nullptr));
|
||||
auto* range_del_iter = m->NewRangeTombstoneIterator(
|
||||
ro, kMaxSequenceNumber, true /* immutable_memtable */);
|
||||
if (logical_strip_timestamp) {
|
||||
memtables.push_back(m->NewTimestampStrippingIterator(
|
||||
ro, /*seqno_to_time_mapping=*/nullptr, &arena,
|
||||
/*prefix_extractor=*/nullptr, ts_sz));
|
||||
} else {
|
||||
memtables.push_back(
|
||||
m->NewIterator(ro, /*seqno_to_time_mapping=*/nullptr, &arena,
|
||||
/*prefix_extractor=*/nullptr));
|
||||
}
|
||||
auto* range_del_iter =
|
||||
logical_strip_timestamp
|
||||
? m->NewTimestampStrippingRangeTombstoneIterator(
|
||||
ro, kMaxSequenceNumber, ts_sz)
|
||||
: m->NewRangeTombstoneIterator(ro, kMaxSequenceNumber,
|
||||
true /* immutable_memtable */);
|
||||
if (range_del_iter != nullptr) {
|
||||
range_del_iters.emplace_back(range_del_iter);
|
||||
}
|
||||
|
||||
@@ -874,9 +874,15 @@ TEST_P(FlushJobTimestampTest, NoKeyExpired) {
|
||||
expected_full_history_ts_low = full_history_ts_low;
|
||||
}
|
||||
InternalKey smallest(smallest_key, curr_seq_ - 1, ValueType::kTypeValue);
|
||||
InternalKey largest(largest_key, kStartSeq, ValueType::kTypeValue);
|
||||
CheckFileMetaData(cfd, smallest, largest, &fmeta);
|
||||
CheckFullHistoryTsLow(cfd, expected_full_history_ts_low);
|
||||
if (!persist_udt_) {
|
||||
InternalKey largest(largest_key, curr_seq_ - 1, ValueType::kTypeValue);
|
||||
CheckFileMetaData(cfd, smallest, largest, &fmeta);
|
||||
CheckFullHistoryTsLow(cfd, expected_full_history_ts_low);
|
||||
} else {
|
||||
InternalKey largest(largest_key, kStartSeq, ValueType::kTypeValue);
|
||||
CheckFileMetaData(cfd, smallest, largest, &fmeta);
|
||||
CheckFullHistoryTsLow(cfd, expected_full_history_ts_low);
|
||||
}
|
||||
}
|
||||
job_context.Clean();
|
||||
ASSERT_TRUE(to_delete.empty());
|
||||
|
||||
+23
-27
@@ -32,11 +32,11 @@ namespace ROCKSDB_NAMESPACE {
|
||||
// iter.Next()
|
||||
class ForwardLevelIterator : public InternalIterator {
|
||||
public:
|
||||
ForwardLevelIterator(
|
||||
const ColumnFamilyData* const cfd, const ReadOptions& read_options,
|
||||
const std::vector<FileMetaData*>& files,
|
||||
const std::shared_ptr<const SliceTransform>& prefix_extractor,
|
||||
bool allow_unprepared_value, uint8_t block_protection_bytes_per_key)
|
||||
ForwardLevelIterator(const ColumnFamilyData* const cfd,
|
||||
const ReadOptions& read_options,
|
||||
const std::vector<FileMetaData*>& files,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
bool allow_unprepared_value)
|
||||
: cfd_(cfd),
|
||||
read_options_(read_options),
|
||||
files_(files),
|
||||
@@ -44,9 +44,8 @@ class ForwardLevelIterator : public InternalIterator {
|
||||
file_index_(std::numeric_limits<uint32_t>::max()),
|
||||
file_iter_(nullptr),
|
||||
pinned_iters_mgr_(nullptr),
|
||||
prefix_extractor_(prefix_extractor),
|
||||
allow_unprepared_value_(allow_unprepared_value),
|
||||
block_protection_bytes_per_key_(block_protection_bytes_per_key) {
|
||||
mutable_cf_options_(mutable_cf_options),
|
||||
allow_unprepared_value_(allow_unprepared_value) {
|
||||
status_.PermitUncheckedError(); // Allow uninitialized status through
|
||||
}
|
||||
|
||||
@@ -83,13 +82,12 @@ class ForwardLevelIterator : public InternalIterator {
|
||||
read_options_, *(cfd_->soptions()), cfd_->internal_comparator(),
|
||||
*files_[file_index_],
|
||||
read_options_.ignore_range_deletions ? nullptr : &range_del_agg,
|
||||
prefix_extractor_, /*table_reader_ptr=*/nullptr,
|
||||
mutable_cf_options_, /*table_reader_ptr=*/nullptr,
|
||||
/*file_read_hist=*/nullptr, TableReaderCaller::kUserIterator,
|
||||
/*arena=*/nullptr, /*skip_filters=*/false, /*level=*/-1,
|
||||
/*max_file_size_for_l0_meta_pin=*/0,
|
||||
/*smallest_compaction_key=*/nullptr,
|
||||
/*largest_compaction_key=*/nullptr, allow_unprepared_value_,
|
||||
block_protection_bytes_per_key_);
|
||||
/*largest_compaction_key=*/nullptr, allow_unprepared_value_);
|
||||
file_iter_->SetPinnedItersMgr(pinned_iters_mgr_);
|
||||
valid_ = false;
|
||||
if (!range_del_agg.IsEmpty()) {
|
||||
@@ -167,6 +165,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_;
|
||||
@@ -210,10 +212,9 @@ class ForwardLevelIterator : public InternalIterator {
|
||||
Status status_;
|
||||
InternalIterator* file_iter_;
|
||||
PinnedIteratorsManager* pinned_iters_mgr_;
|
||||
// Kept alive by ForwardIterator::sv_->mutable_cf_options
|
||||
const std::shared_ptr<const SliceTransform>& prefix_extractor_;
|
||||
const MutableCFOptions& mutable_cf_options_;
|
||||
|
||||
const bool allow_unprepared_value_;
|
||||
const uint8_t block_protection_bytes_per_key_;
|
||||
};
|
||||
|
||||
ForwardIterator::ForwardIterator(DBImpl* db, const ReadOptions& read_options,
|
||||
@@ -746,14 +747,13 @@ void ForwardIterator::RebuildIterators(bool refresh_sv) {
|
||||
l0_iters_.push_back(cfd_->table_cache()->NewIterator(
|
||||
read_options_, *cfd_->soptions(), cfd_->internal_comparator(), *l0,
|
||||
read_options_.ignore_range_deletions ? nullptr : &range_del_agg,
|
||||
sv_->mutable_cf_options.prefix_extractor,
|
||||
sv_->mutable_cf_options,
|
||||
/*table_reader_ptr=*/nullptr, /*file_read_hist=*/nullptr,
|
||||
TableReaderCaller::kUserIterator, /*arena=*/nullptr,
|
||||
/*skip_filters=*/false, /*level=*/-1,
|
||||
MaxFileSizeForL0MetaPin(sv_->mutable_cf_options),
|
||||
/*smallest_compaction_key=*/nullptr,
|
||||
/*largest_compaction_key=*/nullptr, allow_unprepared_value_,
|
||||
sv_->mutable_cf_options.block_protection_bytes_per_key));
|
||||
/*largest_compaction_key=*/nullptr, allow_unprepared_value_));
|
||||
}
|
||||
BuildLevelIterators(vstorage, sv_);
|
||||
current_ = nullptr;
|
||||
@@ -834,14 +834,13 @@ void ForwardIterator::RenewIterators() {
|
||||
read_options_, *cfd_->soptions(), cfd_->internal_comparator(),
|
||||
*l0_files_new[inew],
|
||||
read_options_.ignore_range_deletions ? nullptr : &range_del_agg,
|
||||
svnew->mutable_cf_options.prefix_extractor,
|
||||
svnew->mutable_cf_options,
|
||||
/*table_reader_ptr=*/nullptr, /*file_read_hist=*/nullptr,
|
||||
TableReaderCaller::kUserIterator, /*arena=*/nullptr,
|
||||
/*skip_filters=*/false, /*level=*/-1,
|
||||
MaxFileSizeForL0MetaPin(svnew->mutable_cf_options),
|
||||
/*smallest_compaction_key=*/nullptr,
|
||||
/*largest_compaction_key=*/nullptr, allow_unprepared_value_,
|
||||
svnew->mutable_cf_options.block_protection_bytes_per_key));
|
||||
/*largest_compaction_key=*/nullptr, allow_unprepared_value_));
|
||||
}
|
||||
|
||||
for (auto* f : l0_iters_) {
|
||||
@@ -884,9 +883,8 @@ void ForwardIterator::BuildLevelIterators(const VersionStorageInfo* vstorage,
|
||||
}
|
||||
} else {
|
||||
level_iters_.push_back(new ForwardLevelIterator(
|
||||
cfd_, read_options_, level_files,
|
||||
sv->mutable_cf_options.prefix_extractor, allow_unprepared_value_,
|
||||
sv->mutable_cf_options.block_protection_bytes_per_key));
|
||||
cfd_, read_options_, level_files, sv->mutable_cf_options,
|
||||
allow_unprepared_value_));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -901,15 +899,13 @@ void ForwardIterator::ResetIncompleteIterators() {
|
||||
DeleteIterator(l0_iters_[i]);
|
||||
l0_iters_[i] = cfd_->table_cache()->NewIterator(
|
||||
read_options_, *cfd_->soptions(), cfd_->internal_comparator(),
|
||||
*l0_files[i], /*range_del_agg=*/nullptr,
|
||||
sv_->mutable_cf_options.prefix_extractor,
|
||||
*l0_files[i], /*range_del_agg=*/nullptr, sv_->mutable_cf_options,
|
||||
/*table_reader_ptr=*/nullptr, /*file_read_hist=*/nullptr,
|
||||
TableReaderCaller::kUserIterator, /*arena=*/nullptr,
|
||||
/*skip_filters=*/false, /*level=*/-1,
|
||||
MaxFileSizeForL0MetaPin(sv_->mutable_cf_options),
|
||||
/*smallest_compaction_key=*/nullptr,
|
||||
/*largest_compaction_key=*/nullptr, allow_unprepared_value_,
|
||||
sv_->mutable_cf_options.block_protection_bytes_per_key);
|
||||
/*largest_compaction_key=*/nullptr, allow_unprepared_value_);
|
||||
l0_iters_[i]->SetPinnedItersMgr(pinned_iters_mgr_);
|
||||
}
|
||||
|
||||
|
||||
@@ -329,7 +329,7 @@ Status ImportColumnFamilyJob::GetIngestedFileInfo(
|
||||
// TODO(yuzhangyu): User-defined timestamps doesn't support importing column
|
||||
// family. Pass in the correct `user_defined_timestamps_persisted` flag for
|
||||
// creating `TableReaderOptions` when the support is there.
|
||||
status = cfd_->ioptions()->table_factory->NewTableReader(
|
||||
status = sv->mutable_cf_options.table_factory->NewTableReader(
|
||||
TableReaderOptions(
|
||||
*cfd_->ioptions(), sv->mutable_cf_options.prefix_extractor,
|
||||
env_options_, cfd_->internal_comparator(),
|
||||
@@ -371,7 +371,8 @@ Status ImportColumnFamilyJob::GetIngestedFileInfo(
|
||||
if (iter->Valid()) {
|
||||
file_to_import->smallest_internal_key.DecodeFrom(iter->key());
|
||||
Slice largest;
|
||||
if (strcmp(cfd_->ioptions()->table_factory->Name(), "PlainTable") == 0) {
|
||||
if (strcmp(sv->mutable_cf_options.table_factory->Name(), "PlainTable") ==
|
||||
0) {
|
||||
// PlainTable iterator does not support SeekToLast().
|
||||
largest = iter->key();
|
||||
for (; iter->Valid(); iter->Next()) {
|
||||
|
||||
@@ -951,6 +951,8 @@ TEST_F(ImportColumnFamilyTest, AssignEpochNumberToMultipleCF) {
|
||||
Options options = CurrentOptions();
|
||||
options.level_compaction_dynamic_level_bytes = true;
|
||||
options.max_background_jobs = 8;
|
||||
// Always allow parallel compaction
|
||||
options.soft_pending_compaction_bytes_limit = 10;
|
||||
env_->SetBackgroundThreads(2, Env::LOW);
|
||||
env_->SetBackgroundThreads(0, Env::BOTTOM);
|
||||
CreateAndReopenWithCF({"CF1", "CF2"}, options);
|
||||
|
||||
@@ -1495,8 +1495,10 @@ bool InternalStats::HandleEstimateOldestKeyTime(uint64_t* value, DBImpl* /*db*/,
|
||||
}
|
||||
|
||||
Cache* InternalStats::GetBlockCacheForStats() {
|
||||
auto* table_factory = cfd_->ioptions()->table_factory.get();
|
||||
// NOTE: called in startup before GetCurrentMutableCFOptions() is ready
|
||||
auto* table_factory = cfd_->GetLatestMutableCFOptions()->table_factory.get();
|
||||
assert(table_factory != nullptr);
|
||||
// FIXME: need to a shared_ptr if/when block_cache is going to be mutable
|
||||
return table_factory->GetOptions<Cache>(TableFactory::kBlockCacheOpts());
|
||||
}
|
||||
|
||||
@@ -2161,7 +2163,8 @@ class BlockCachePropertyAggregator : public IntPropertyAggregator {
|
||||
virtual ~BlockCachePropertyAggregator() override = default;
|
||||
|
||||
void Add(ColumnFamilyData* cfd, uint64_t value) override {
|
||||
auto* table_factory = cfd->ioptions()->table_factory.get();
|
||||
auto* table_factory =
|
||||
cfd->GetCurrentMutableCFOptions()->table_factory.get();
|
||||
assert(table_factory != nullptr);
|
||||
Cache* cache =
|
||||
table_factory->GetOptions<Cache>(TableFactory::kBlockCacheOpts());
|
||||
|
||||
@@ -202,6 +202,10 @@ struct JobContext {
|
||||
// that corresponds to the set of files in 'live'.
|
||||
uint64_t manifest_file_number;
|
||||
uint64_t pending_manifest_file_number;
|
||||
|
||||
// Used for remote compaction. To prevent OPTIONS files from getting
|
||||
// purged by PurgeObsoleteFiles() of the primary host
|
||||
uint64_t min_options_file_number;
|
||||
uint64_t log_number;
|
||||
uint64_t prev_log_number;
|
||||
|
||||
|
||||
+153
@@ -613,6 +613,135 @@ InternalIterator* MemTable::NewIterator(
|
||||
seqno_to_time_mapping, arena, prefix_extractor);
|
||||
}
|
||||
|
||||
// An iterator wrapper that wraps a MemTableIterator and logically strips each
|
||||
// key's user-defined timestamp.
|
||||
class TimestampStrippingIterator : public InternalIterator {
|
||||
public:
|
||||
TimestampStrippingIterator(
|
||||
MemTableIterator::Kind kind, const MemTable& memtable,
|
||||
const ReadOptions& read_options,
|
||||
UnownedPtr<const SeqnoToTimeMapping> seqno_to_time_mapping, Arena* arena,
|
||||
const SliceTransform* cf_prefix_extractor, size_t ts_sz)
|
||||
: arena_mode_(arena != nullptr), kind_(kind), ts_sz_(ts_sz) {
|
||||
assert(ts_sz_ != 0);
|
||||
void* mem = arena ? arena->AllocateAligned(sizeof(MemTableIterator)) :
|
||||
operator new(sizeof(MemTableIterator));
|
||||
iter_ = new (mem)
|
||||
MemTableIterator(kind, memtable, read_options, seqno_to_time_mapping,
|
||||
arena, cf_prefix_extractor);
|
||||
}
|
||||
|
||||
// No copying allowed
|
||||
TimestampStrippingIterator(const TimestampStrippingIterator&) = delete;
|
||||
void operator=(const TimestampStrippingIterator&) = delete;
|
||||
|
||||
~TimestampStrippingIterator() override {
|
||||
if (arena_mode_) {
|
||||
iter_->~MemTableIterator();
|
||||
} else {
|
||||
delete iter_;
|
||||
}
|
||||
}
|
||||
|
||||
void SetPinnedItersMgr(PinnedIteratorsManager* pinned_iters_mgr) override {
|
||||
iter_->SetPinnedItersMgr(pinned_iters_mgr);
|
||||
}
|
||||
|
||||
bool Valid() const override { return iter_->Valid(); }
|
||||
void Seek(const Slice& k) override {
|
||||
iter_->Seek(k);
|
||||
UpdateKeyAndValueBuffer();
|
||||
}
|
||||
void SeekForPrev(const Slice& k) override {
|
||||
iter_->SeekForPrev(k);
|
||||
UpdateKeyAndValueBuffer();
|
||||
}
|
||||
void SeekToFirst() override {
|
||||
iter_->SeekToFirst();
|
||||
UpdateKeyAndValueBuffer();
|
||||
}
|
||||
void SeekToLast() override {
|
||||
iter_->SeekToLast();
|
||||
UpdateKeyAndValueBuffer();
|
||||
}
|
||||
void Next() override {
|
||||
iter_->Next();
|
||||
UpdateKeyAndValueBuffer();
|
||||
}
|
||||
bool NextAndGetResult(IterateResult* result) override {
|
||||
iter_->Next();
|
||||
UpdateKeyAndValueBuffer();
|
||||
bool is_valid = Valid();
|
||||
if (is_valid) {
|
||||
result->key = key();
|
||||
result->bound_check_result = IterBoundCheck::kUnknown;
|
||||
result->value_prepared = true;
|
||||
}
|
||||
return is_valid;
|
||||
}
|
||||
void Prev() override {
|
||||
iter_->Prev();
|
||||
UpdateKeyAndValueBuffer();
|
||||
}
|
||||
Slice key() const override {
|
||||
assert(Valid());
|
||||
return key_buf_;
|
||||
}
|
||||
|
||||
uint64_t write_unix_time() const override { return iter_->write_unix_time(); }
|
||||
Slice value() const override {
|
||||
if (kind_ == MemTableIterator::Kind::kRangeDelEntries) {
|
||||
return value_buf_;
|
||||
}
|
||||
return iter_->value();
|
||||
}
|
||||
Status status() const override { return iter_->status(); }
|
||||
bool IsKeyPinned() const override {
|
||||
// Key is only in a buffer that is updated in each iteration.
|
||||
return false;
|
||||
}
|
||||
bool IsValuePinned() const override {
|
||||
if (kind_ == MemTableIterator::Kind::kRangeDelEntries) {
|
||||
return false;
|
||||
}
|
||||
return iter_->IsValuePinned();
|
||||
}
|
||||
|
||||
private:
|
||||
void UpdateKeyAndValueBuffer() {
|
||||
key_buf_.clear();
|
||||
if (kind_ == MemTableIterator::Kind::kRangeDelEntries) {
|
||||
value_buf_.clear();
|
||||
}
|
||||
if (!Valid()) {
|
||||
return;
|
||||
}
|
||||
Slice original_key = iter_->key();
|
||||
ReplaceInternalKeyWithMinTimestamp(&key_buf_, original_key, ts_sz_);
|
||||
if (kind_ == MemTableIterator::Kind::kRangeDelEntries) {
|
||||
Slice original_value = iter_->value();
|
||||
AppendUserKeyWithMinTimestamp(&value_buf_, original_value, ts_sz_);
|
||||
}
|
||||
}
|
||||
bool arena_mode_;
|
||||
MemTableIterator::Kind kind_;
|
||||
size_t ts_sz_;
|
||||
MemTableIterator* iter_;
|
||||
std::string key_buf_;
|
||||
std::string value_buf_;
|
||||
};
|
||||
|
||||
InternalIterator* MemTable::NewTimestampStrippingIterator(
|
||||
const ReadOptions& read_options,
|
||||
UnownedPtr<const SeqnoToTimeMapping> seqno_to_time_mapping, Arena* arena,
|
||||
const SliceTransform* prefix_extractor, size_t ts_sz) {
|
||||
assert(arena != nullptr);
|
||||
auto mem = arena->AllocateAligned(sizeof(TimestampStrippingIterator));
|
||||
return new (mem) TimestampStrippingIterator(
|
||||
MemTableIterator::kPointEntries, *this, read_options,
|
||||
seqno_to_time_mapping, arena, prefix_extractor, ts_sz);
|
||||
}
|
||||
|
||||
FragmentedRangeTombstoneIterator* MemTable::NewRangeTombstoneIterator(
|
||||
const ReadOptions& read_options, SequenceNumber read_seq,
|
||||
bool immutable_memtable) {
|
||||
@@ -624,6 +753,30 @@ FragmentedRangeTombstoneIterator* MemTable::NewRangeTombstoneIterator(
|
||||
immutable_memtable);
|
||||
}
|
||||
|
||||
FragmentedRangeTombstoneIterator*
|
||||
MemTable::NewTimestampStrippingRangeTombstoneIterator(
|
||||
const ReadOptions& read_options, SequenceNumber read_seq, size_t ts_sz) {
|
||||
if (read_options.ignore_range_deletions ||
|
||||
is_range_del_table_empty_.load(std::memory_order_relaxed)) {
|
||||
return nullptr;
|
||||
}
|
||||
if (!timestamp_stripping_fragmented_range_tombstone_list_) {
|
||||
// TODO: plumb Env::IOActivity, Env::IOPriority
|
||||
auto* unfragmented_iter = new TimestampStrippingIterator(
|
||||
MemTableIterator::kRangeDelEntries, *this, ReadOptions(),
|
||||
/*seqno_to_time_mapping*/ nullptr, /* arena */ nullptr,
|
||||
/* prefix_extractor */ nullptr, ts_sz);
|
||||
|
||||
timestamp_stripping_fragmented_range_tombstone_list_ =
|
||||
std::make_unique<FragmentedRangeTombstoneList>(
|
||||
std::unique_ptr<InternalIterator>(unfragmented_iter),
|
||||
comparator_.comparator);
|
||||
}
|
||||
return new FragmentedRangeTombstoneIterator(
|
||||
timestamp_stripping_fragmented_range_tombstone_list_.get(),
|
||||
comparator_.comparator, read_seq, read_options.timestamp);
|
||||
}
|
||||
|
||||
FragmentedRangeTombstoneIterator* MemTable::NewRangeTombstoneIteratorInternal(
|
||||
const ReadOptions& read_options, SequenceNumber read_seq,
|
||||
bool immutable_memtable) {
|
||||
|
||||
@@ -213,6 +213,14 @@ class MemTable {
|
||||
UnownedPtr<const SeqnoToTimeMapping> seqno_to_time_mapping, Arena* arena,
|
||||
const SliceTransform* prefix_extractor);
|
||||
|
||||
// Returns an iterator that wraps a MemTableIterator and logically strips the
|
||||
// user-defined timestamp of each key. This API is only used by flush when
|
||||
// user-defined timestamps in MemTable only feature is enabled.
|
||||
InternalIterator* NewTimestampStrippingIterator(
|
||||
const ReadOptions& read_options,
|
||||
UnownedPtr<const SeqnoToTimeMapping> seqno_to_time_mapping, Arena* arena,
|
||||
const SliceTransform* prefix_extractor, size_t ts_sz);
|
||||
|
||||
// Returns an iterator that yields the range tombstones of the memtable.
|
||||
// The caller must ensure that the underlying MemTable remains live
|
||||
// while the returned iterator is live.
|
||||
@@ -227,6 +235,13 @@ class MemTable {
|
||||
const ReadOptions& read_options, SequenceNumber read_seq,
|
||||
bool immutable_memtable);
|
||||
|
||||
// Returns an iterator that yields the range tombstones of the memtable and
|
||||
// logically strips the user-defined timestamp of each key (including start
|
||||
// key, and end key). This API is only used by flush when user-defined
|
||||
// timestamps in MemTable only feature is enabled.
|
||||
FragmentedRangeTombstoneIterator* NewTimestampStrippingRangeTombstoneIterator(
|
||||
const ReadOptions& read_options, SequenceNumber read_seq, size_t ts_sz);
|
||||
|
||||
Status VerifyEncodedEntry(Slice encoded,
|
||||
const ProtectionInfoKVOS64& kv_prot_info);
|
||||
|
||||
@@ -704,6 +719,12 @@ class MemTable {
|
||||
std::unique_ptr<FragmentedRangeTombstoneList>
|
||||
fragmented_range_tombstone_list_;
|
||||
|
||||
// The fragmented range tombstone of this memtable with all keys' user-defined
|
||||
// timestamps logically stripped. This is constructed and used by flush when
|
||||
// user-defined timestamps in memtable only feature is enabled.
|
||||
std::unique_ptr<FragmentedRangeTombstoneList>
|
||||
timestamp_stripping_fragmented_range_tombstone_list_;
|
||||
|
||||
// makes sure there is a single range tombstone writer to invalidate cache
|
||||
std::mutex range_del_mutex_;
|
||||
CoreLocalArray<std::shared_ptr<FragmentedRangeTombstoneListCache>>
|
||||
|
||||
+14
-1
@@ -359,11 +359,15 @@ bool MemTableListVersion::MemtableLimitExceeded(size_t usage) {
|
||||
}
|
||||
}
|
||||
|
||||
bool MemTableListVersion::HistoryShouldBeTrimmed(size_t usage) {
|
||||
return MemtableLimitExceeded(usage) && !memlist_history_.empty();
|
||||
}
|
||||
|
||||
// Make sure we don't use up too much space in history
|
||||
bool MemTableListVersion::TrimHistory(autovector<MemTable*>* to_delete,
|
||||
size_t usage) {
|
||||
bool ret = false;
|
||||
while (MemtableLimitExceeded(usage) && !memlist_history_.empty()) {
|
||||
while (HistoryShouldBeTrimmed(usage)) {
|
||||
MemTable* x = memlist_history_.back();
|
||||
memlist_history_.pop_back();
|
||||
|
||||
@@ -661,8 +665,16 @@ void MemTableList::Add(MemTable* m, autovector<MemTable*>* to_delete) {
|
||||
}
|
||||
|
||||
bool MemTableList::TrimHistory(autovector<MemTable*>* to_delete, size_t usage) {
|
||||
// Check if history trim is needed first, so that we can avoid installing a
|
||||
// new MemTableListVersion without installing a SuperVersion (installed based
|
||||
// on return value of this function).
|
||||
if (!current_->HistoryShouldBeTrimmed(usage)) {
|
||||
ResetTrimHistoryNeeded();
|
||||
return false;
|
||||
}
|
||||
InstallNewVersion();
|
||||
bool ret = current_->TrimHistory(to_delete, usage);
|
||||
assert(ret);
|
||||
UpdateCachedValuesFromMemTableListVersion();
|
||||
ResetTrimHistoryNeeded();
|
||||
return ret;
|
||||
@@ -714,6 +726,7 @@ void MemTableList::InstallNewVersion() {
|
||||
// somebody else holds the current version, we need to create new one
|
||||
MemTableListVersion* version = current_;
|
||||
current_ = new MemTableListVersion(¤t_memory_usage_, *version);
|
||||
current_->SetID(++last_memtable_list_version_id_);
|
||||
current_->Ref();
|
||||
version->Unref();
|
||||
}
|
||||
|
||||
+19
-2
@@ -141,6 +141,11 @@ class MemTableListVersion {
|
||||
// Return kMaxSequenceNumber if the list is empty.
|
||||
SequenceNumber GetFirstSequenceNumber() const;
|
||||
|
||||
// REQUIRES: db_mutex held.
|
||||
void SetID(uint64_t id) { id_ = id; }
|
||||
|
||||
uint64_t GetID() const { return id_; }
|
||||
|
||||
private:
|
||||
friend class MemTableList;
|
||||
|
||||
@@ -161,7 +166,11 @@ class MemTableListVersion {
|
||||
// REQUIRE: m is an immutable memtable
|
||||
void Remove(MemTable* m, autovector<MemTable*>* to_delete);
|
||||
|
||||
// Return true if memtable is trimmed
|
||||
// Return true if the memtable list should be trimmed to get memory usage
|
||||
// under budget.
|
||||
bool HistoryShouldBeTrimmed(size_t usage);
|
||||
|
||||
// Trim history, Return true if memtable is trimmed
|
||||
bool TrimHistory(autovector<MemTable*>* to_delete, size_t usage);
|
||||
|
||||
bool GetFromList(std::list<MemTable*>* list, const LookupKey& key,
|
||||
@@ -205,6 +214,9 @@ class MemTableListVersion {
|
||||
int refs_ = 0;
|
||||
|
||||
size_t* parent_memtable_list_memory_usage_;
|
||||
|
||||
// MemtableListVersion id to track for flush results checking.
|
||||
uint64_t id_ = 0;
|
||||
};
|
||||
|
||||
// This class stores references to all the immutable memtables.
|
||||
@@ -235,7 +247,8 @@ class MemTableList {
|
||||
flush_requested_(false),
|
||||
current_memory_usage_(0),
|
||||
current_memory_allocted_bytes_excluding_last_(0),
|
||||
current_has_history_(false) {
|
||||
current_has_history_(false),
|
||||
last_memtable_list_version_id_(0) {
|
||||
current_->Ref();
|
||||
}
|
||||
|
||||
@@ -500,6 +513,10 @@ class MemTableList {
|
||||
|
||||
// Cached value of current_->HasHistory().
|
||||
std::atomic<bool> current_has_history_;
|
||||
|
||||
// Last memtabe list version id, increase by 1 each time a new
|
||||
// MemtableListVersion is installed.
|
||||
uint64_t last_memtable_list_version_id_;
|
||||
};
|
||||
|
||||
// Installs memtable atomic flush results.
|
||||
|
||||
+118
-60
@@ -21,27 +21,21 @@ struct MultiCfIteratorInfo {
|
||||
int order;
|
||||
};
|
||||
|
||||
template <typename ResetFunc, typename PopulateFunc>
|
||||
class MultiCfIteratorImpl {
|
||||
public:
|
||||
MultiCfIteratorImpl(
|
||||
const Comparator* comparator,
|
||||
const std::vector<ColumnFamilyHandle*>& column_families,
|
||||
const std::vector<Iterator*>& child_iterators,
|
||||
std::function<void()> reset_func,
|
||||
std::function<void(const autovector<MultiCfIteratorInfo>&)> populate_func)
|
||||
: comparator_(comparator),
|
||||
heap_(MultiCfMinHeap(
|
||||
MultiCfHeapItemComparator<std::greater<int>>(comparator_))),
|
||||
const ReadOptions& read_options, const Comparator* comparator,
|
||||
std::vector<std::pair<ColumnFamilyHandle*, std::unique_ptr<Iterator>>>&&
|
||||
cfh_iter_pairs,
|
||||
ResetFunc reset_func, PopulateFunc populate_func)
|
||||
: allow_unprepared_value_(read_options.allow_unprepared_value),
|
||||
comparator_(comparator),
|
||||
cfh_iter_pairs_(std::move(cfh_iter_pairs)),
|
||||
reset_func_(std::move(reset_func)),
|
||||
populate_func_(std::move(populate_func)) {
|
||||
assert(column_families.size() > 0 &&
|
||||
column_families.size() == child_iterators.size());
|
||||
cfh_iter_pairs_.reserve(column_families.size());
|
||||
for (size_t i = 0; i < column_families.size(); ++i) {
|
||||
cfh_iter_pairs_.emplace_back(
|
||||
column_families[i], std::unique_ptr<Iterator>(child_iterators[i]));
|
||||
}
|
||||
}
|
||||
populate_func_(std::move(populate_func)),
|
||||
heap_(MultiCfMinHeap(
|
||||
MultiCfHeapItemComparator<std::greater<int>>(comparator_))) {}
|
||||
~MultiCfIteratorImpl() { status_.PermitUncheckedError(); }
|
||||
|
||||
// No copy allowed
|
||||
@@ -101,10 +95,28 @@ class MultiCfIteratorImpl {
|
||||
AdvanceIterator(max_heap, [](Iterator* iter) { iter->Prev(); });
|
||||
}
|
||||
|
||||
bool PrepareValue() {
|
||||
assert(Valid());
|
||||
|
||||
if (!allow_unprepared_value_) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (std::holds_alternative<MultiCfMaxHeap>(heap_)) {
|
||||
return PopulateIterator(std::get<MultiCfMaxHeap>(heap_));
|
||||
}
|
||||
|
||||
return PopulateIterator(std::get<MultiCfMinHeap>(heap_));
|
||||
}
|
||||
|
||||
private:
|
||||
Status status_;
|
||||
bool allow_unprepared_value_;
|
||||
const Comparator* comparator_;
|
||||
std::vector<std::pair<ColumnFamilyHandle*, std::unique_ptr<Iterator>>>
|
||||
cfh_iter_pairs_;
|
||||
Status status_;
|
||||
ResetFunc reset_func_;
|
||||
PopulateFunc populate_func_;
|
||||
|
||||
template <typename CompareOp>
|
||||
class MultiCfHeapItemComparator {
|
||||
@@ -125,7 +137,7 @@ class MultiCfIteratorImpl {
|
||||
private:
|
||||
const Comparator* comparator_;
|
||||
};
|
||||
const Comparator* comparator_;
|
||||
|
||||
using MultiCfMinHeap =
|
||||
BinaryHeap<MultiCfIteratorInfo,
|
||||
MultiCfHeapItemComparator<std::greater<int>>>;
|
||||
@@ -136,9 +148,6 @@ class MultiCfIteratorImpl {
|
||||
|
||||
MultiCfIterHeap heap_;
|
||||
|
||||
std::function<void()> reset_func_;
|
||||
std::function<void(autovector<MultiCfIteratorInfo>)> populate_func_;
|
||||
|
||||
Iterator* current() const {
|
||||
if (std::holds_alternative<MultiCfMaxHeap>(heap_)) {
|
||||
auto& max_heap = std::get<MultiCfMaxHeap>(heap_);
|
||||
@@ -163,11 +172,11 @@ class MultiCfIteratorImpl {
|
||||
}
|
||||
|
||||
void InitMinHeap() {
|
||||
heap_.emplace<MultiCfMinHeap>(
|
||||
heap_.template emplace<MultiCfMinHeap>(
|
||||
MultiCfHeapItemComparator<std::greater<int>>(comparator_));
|
||||
}
|
||||
void InitMaxHeap() {
|
||||
heap_.emplace<MultiCfMaxHeap>(
|
||||
heap_.template emplace<MultiCfMaxHeap>(
|
||||
MultiCfHeapItemComparator<std::less<int>>(comparator_));
|
||||
}
|
||||
|
||||
@@ -186,13 +195,14 @@ class MultiCfIteratorImpl {
|
||||
if (!status_.ok()) {
|
||||
// Non-OK status from the iterator. Bail out early
|
||||
heap.clear();
|
||||
break;
|
||||
return;
|
||||
}
|
||||
}
|
||||
++i;
|
||||
}
|
||||
if (!heap.empty()) {
|
||||
PopulateIterator(heap);
|
||||
if (!allow_unprepared_value_ && !heap.empty()) {
|
||||
[[maybe_unused]] const bool result = PopulateIterator(heap);
|
||||
assert(result || (!Valid() && !status_.ok()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,32 +222,41 @@ class MultiCfIteratorImpl {
|
||||
// 2. Make sure all others have iterated past the top iterator key slice
|
||||
// 3. Advance the top iterator, and add it back to the heap if valid
|
||||
auto top = heap.top();
|
||||
assert(top.iterator);
|
||||
assert(top.iterator->Valid());
|
||||
assert(top.iterator->status().ok());
|
||||
|
||||
heap.pop();
|
||||
if (!heap.empty()) {
|
||||
|
||||
while (!heap.empty()) {
|
||||
auto current = heap.top();
|
||||
assert(current.iterator);
|
||||
while (current.iterator->Valid() &&
|
||||
comparator_->Compare(top.iterator->key(),
|
||||
current.iterator->key()) == 0) {
|
||||
assert(current.iterator->Valid());
|
||||
assert(current.iterator->status().ok());
|
||||
|
||||
if (comparator_->Compare(current.iterator->key(), top.iterator->key()) !=
|
||||
0) {
|
||||
break;
|
||||
}
|
||||
|
||||
advance_func(current.iterator);
|
||||
|
||||
if (current.iterator->Valid()) {
|
||||
assert(current.iterator->status().ok());
|
||||
advance_func(current.iterator);
|
||||
if (current.iterator->Valid()) {
|
||||
heap.replace_top(heap.top());
|
||||
heap.replace_top(current);
|
||||
} else {
|
||||
considerStatus(current.iterator->status());
|
||||
if (!status_.ok()) {
|
||||
heap.clear();
|
||||
return;
|
||||
} else {
|
||||
considerStatus(current.iterator->status());
|
||||
if (!status_.ok()) {
|
||||
heap.clear();
|
||||
return;
|
||||
} else {
|
||||
heap.pop();
|
||||
}
|
||||
}
|
||||
if (!heap.empty()) {
|
||||
current = heap.top();
|
||||
heap.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
advance_func(top.iterator);
|
||||
|
||||
if (top.iterator->Valid()) {
|
||||
assert(top.iterator->status().ok());
|
||||
heap.push(top);
|
||||
@@ -249,13 +268,14 @@ class MultiCfIteratorImpl {
|
||||
}
|
||||
}
|
||||
|
||||
if (!heap.empty()) {
|
||||
PopulateIterator(heap);
|
||||
if (!allow_unprepared_value_ && !heap.empty()) {
|
||||
[[maybe_unused]] const bool result = PopulateIterator(heap);
|
||||
assert(result || (!Valid() && !status_.ok()));
|
||||
}
|
||||
}
|
||||
|
||||
template <typename BinaryHeap>
|
||||
void PopulateIterator(BinaryHeap& heap) {
|
||||
bool PopulateIterator(BinaryHeap& heap) {
|
||||
// 1. Keep the top iterator (by popping it from the heap) and add it to list
|
||||
// to populate
|
||||
// 2. For all non-top iterators having the same key as top iter popped
|
||||
@@ -265,31 +285,69 @@ class MultiCfIteratorImpl {
|
||||
// populate the value/columns and attribute_groups from the list
|
||||
// collected in step 1 and 2 and add all the iters back to the heap
|
||||
assert(!heap.empty());
|
||||
|
||||
auto prepare_value = [this, &heap](Iterator* iterator) {
|
||||
assert(iterator);
|
||||
assert(iterator->Valid());
|
||||
assert(iterator->status().ok());
|
||||
|
||||
if (!iterator->PrepareValue()) {
|
||||
assert(!iterator->Valid());
|
||||
assert(!iterator->status().ok());
|
||||
|
||||
considerStatus(iterator->status());
|
||||
heap.clear();
|
||||
|
||||
assert(!Valid());
|
||||
assert(!status_.ok());
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
auto top = heap.top();
|
||||
heap.pop();
|
||||
assert(top.iterator);
|
||||
assert(top.iterator->Valid());
|
||||
assert(top.iterator->status().ok());
|
||||
|
||||
if (!prepare_value(top.iterator)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
autovector<MultiCfIteratorInfo> to_populate;
|
||||
|
||||
to_populate.push_back(top);
|
||||
if (!heap.empty()) {
|
||||
heap.pop();
|
||||
|
||||
while (!heap.empty()) {
|
||||
auto current = heap.top();
|
||||
assert(current.iterator);
|
||||
while (current.iterator->Valid() &&
|
||||
comparator_->Compare(top.iterator->key(),
|
||||
current.iterator->key()) == 0) {
|
||||
assert(current.iterator->status().ok());
|
||||
to_populate.push_back(current);
|
||||
heap.pop();
|
||||
if (!heap.empty()) {
|
||||
current = heap.top();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
assert(current.iterator->Valid());
|
||||
assert(current.iterator->status().ok());
|
||||
|
||||
if (comparator_->Compare(current.iterator->key(), top.iterator->key()) !=
|
||||
0) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (!prepare_value(current.iterator)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
to_populate.push_back(current);
|
||||
heap.pop();
|
||||
}
|
||||
|
||||
// Add the items back to the heap
|
||||
for (auto& item : to_populate) {
|
||||
heap.push(item);
|
||||
}
|
||||
|
||||
populate_func_(to_populate);
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+349
-94
@@ -15,53 +15,74 @@ class CoalescingIteratorTest : public DBTestBase {
|
||||
|
||||
// Verify Iteration of CoalescingIterator
|
||||
// by SeekToFirst() + Next() and SeekToLast() + Prev()
|
||||
void verifyCoalescingIterator(const std::vector<ColumnFamilyHandle*>& cfhs,
|
||||
void VerifyCoalescingIterator(const std::vector<ColumnFamilyHandle*>& cfhs,
|
||||
const std::vector<Slice>& expected_keys,
|
||||
const std::vector<Slice>& expected_values,
|
||||
const std::optional<std::vector<WideColumns>>&
|
||||
expected_wide_columns = std::nullopt,
|
||||
const Slice* lower_bound = nullptr,
|
||||
const Slice* upper_bound = nullptr) {
|
||||
int i = 0;
|
||||
const Slice* upper_bound = nullptr,
|
||||
bool allow_unprepared_value = false) {
|
||||
const size_t num_keys = expected_keys.size();
|
||||
|
||||
ReadOptions read_options;
|
||||
read_options.iterate_lower_bound = lower_bound;
|
||||
read_options.iterate_upper_bound = upper_bound;
|
||||
read_options.allow_unprepared_value = allow_unprepared_value;
|
||||
|
||||
std::unique_ptr<Iterator> iter =
|
||||
db_->NewCoalescingIterator(read_options, cfhs);
|
||||
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
|
||||
ASSERT_EQ(expected_keys[i], iter->key());
|
||||
ASSERT_EQ(expected_values[i], iter->value());
|
||||
if (expected_wide_columns.has_value()) {
|
||||
ASSERT_EQ(expected_wide_columns.value()[i], iter->columns());
|
||||
}
|
||||
++i;
|
||||
}
|
||||
ASSERT_EQ(expected_keys.size(), i);
|
||||
ASSERT_OK(iter->status());
|
||||
|
||||
int rev_i = i - 1;
|
||||
for (iter->SeekToLast(); iter->Valid(); iter->Prev()) {
|
||||
ASSERT_EQ(expected_keys[rev_i], iter->key());
|
||||
ASSERT_EQ(expected_values[rev_i], iter->value());
|
||||
if (expected_wide_columns.has_value()) {
|
||||
ASSERT_EQ(expected_wide_columns.value()[rev_i], iter->columns());
|
||||
auto check_iter_entry = [&](size_t idx) {
|
||||
ASSERT_EQ(iter->key(), expected_keys[idx]);
|
||||
|
||||
if (allow_unprepared_value) {
|
||||
ASSERT_TRUE(iter->value().empty());
|
||||
ASSERT_TRUE(iter->PrepareValue());
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
}
|
||||
rev_i--;
|
||||
|
||||
ASSERT_EQ(iter->value(), expected_values[idx]);
|
||||
if (expected_wide_columns.has_value()) {
|
||||
ASSERT_EQ(iter->columns(), expected_wide_columns.value()[idx]);
|
||||
}
|
||||
};
|
||||
|
||||
{
|
||||
size_t i = 0;
|
||||
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
|
||||
check_iter_entry(i);
|
||||
++i;
|
||||
}
|
||||
|
||||
ASSERT_EQ(num_keys, i);
|
||||
ASSERT_OK(iter->status());
|
||||
}
|
||||
|
||||
{
|
||||
size_t i = 0;
|
||||
for (iter->SeekToLast(); iter->Valid(); iter->Prev()) {
|
||||
check_iter_entry(num_keys - 1 - i);
|
||||
++i;
|
||||
}
|
||||
|
||||
ASSERT_EQ(num_keys, i);
|
||||
ASSERT_OK(iter->status());
|
||||
}
|
||||
ASSERT_OK(iter->status());
|
||||
}
|
||||
|
||||
void verifyExpectedKeys(ColumnFamilyHandle* cfh,
|
||||
void VerifyExpectedKeys(ColumnFamilyHandle* cfh,
|
||||
const std::vector<Slice>& expected_keys) {
|
||||
int i = 0;
|
||||
Iterator* iter = db_->NewIterator(ReadOptions(), cfh);
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(ReadOptions(), cfh));
|
||||
|
||||
size_t i = 0;
|
||||
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
|
||||
ASSERT_EQ(expected_keys[i], iter->key());
|
||||
ASSERT_EQ(iter->key(), expected_keys[i]);
|
||||
++i;
|
||||
}
|
||||
ASSERT_EQ(expected_keys.size(), i);
|
||||
|
||||
ASSERT_EQ(i, expected_keys.size());
|
||||
ASSERT_OK(iter->status());
|
||||
delete iter;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -96,7 +117,7 @@ TEST_F(CoalescingIteratorTest, SimpleValues) {
|
||||
// Test for iteration over CF default->1->2->3
|
||||
std::vector<ColumnFamilyHandle*> cfhs_order_0_1_2_3 = {
|
||||
handles_[0], handles_[1], handles_[2], handles_[3]};
|
||||
verifyCoalescingIterator(cfhs_order_0_1_2_3, expected_keys,
|
||||
VerifyCoalescingIterator(cfhs_order_0_1_2_3, expected_keys,
|
||||
expected_values);
|
||||
|
||||
// Test for iteration over CF 3->1->default_cf->2
|
||||
@@ -104,7 +125,7 @@ TEST_F(CoalescingIteratorTest, SimpleValues) {
|
||||
handles_[3], handles_[1], handles_[0], handles_[2]};
|
||||
// Iteration order and the return values should be the same since keys are
|
||||
// unique per CF
|
||||
verifyCoalescingIterator(cfhs_order_3_1_0_2, expected_keys,
|
||||
VerifyCoalescingIterator(cfhs_order_3_1_0_2, expected_keys,
|
||||
expected_values);
|
||||
|
||||
// Verify Seek()
|
||||
@@ -163,14 +184,14 @@ TEST_F(CoalescingIteratorTest, SimpleValues) {
|
||||
handles_[0], handles_[1], handles_[2], handles_[3]};
|
||||
std::vector<Slice> expected_values = {"key_1_cf_3_val", "key_2_cf_2_val",
|
||||
"key_3_cf_3_val"};
|
||||
verifyCoalescingIterator(cfhs_order_0_1_2_3, expected_keys,
|
||||
VerifyCoalescingIterator(cfhs_order_0_1_2_3, expected_keys,
|
||||
expected_values);
|
||||
|
||||
// Test for iteration over CFs 3->2->default_cf->1
|
||||
std::vector<ColumnFamilyHandle*> cfhs_order_3_2_0_1 = {
|
||||
handles_[3], handles_[2], handles_[0], handles_[1]};
|
||||
expected_values = {"key_1_cf_0_val", "key_2_cf_1_val", "key_3_cf_1_val"};
|
||||
verifyCoalescingIterator(cfhs_order_3_2_0_1, expected_keys,
|
||||
VerifyCoalescingIterator(cfhs_order_3_2_0_1, expected_keys,
|
||||
expected_values);
|
||||
|
||||
// Verify Seek()
|
||||
@@ -227,7 +248,7 @@ TEST_F(CoalescingIteratorTest, LowerAndUpperBounds) {
|
||||
std::vector<Slice> expected_keys = {"key_2", "key_3", "key_4"};
|
||||
std::vector<Slice> expected_values = {"key_2_cf_1_val", "key_3_cf_2_val",
|
||||
"key_4_cf_3_val"};
|
||||
verifyCoalescingIterator(cfhs_order_0_1_2_3, expected_keys,
|
||||
VerifyCoalescingIterator(cfhs_order_0_1_2_3, expected_keys,
|
||||
expected_values, std::nullopt, &lb);
|
||||
}
|
||||
// with upper_bound
|
||||
@@ -236,7 +257,7 @@ TEST_F(CoalescingIteratorTest, LowerAndUpperBounds) {
|
||||
Slice ub = Slice("key_3");
|
||||
std::vector<Slice> expected_keys = {"key_1", "key_2"};
|
||||
std::vector<Slice> expected_values = {"key_1_cf_0_val", "key_2_cf_1_val"};
|
||||
verifyCoalescingIterator(cfhs_order_0_1_2_3, expected_keys,
|
||||
VerifyCoalescingIterator(cfhs_order_0_1_2_3, expected_keys,
|
||||
expected_values, std::nullopt, nullptr, &ub);
|
||||
}
|
||||
// with lower and upper bound
|
||||
@@ -245,7 +266,7 @@ TEST_F(CoalescingIteratorTest, LowerAndUpperBounds) {
|
||||
Slice ub = Slice("key_4");
|
||||
std::vector<Slice> expected_keys = {"key_2", "key_3"};
|
||||
std::vector<Slice> expected_values = {"key_2_cf_1_val", "key_3_cf_2_val"};
|
||||
verifyCoalescingIterator(cfhs_order_0_1_2_3, expected_keys,
|
||||
VerifyCoalescingIterator(cfhs_order_0_1_2_3, expected_keys,
|
||||
expected_values, std::nullopt, &lb, &ub);
|
||||
}
|
||||
|
||||
@@ -312,7 +333,7 @@ TEST_F(CoalescingIteratorTest, LowerAndUpperBounds) {
|
||||
Slice lb = Slice("key_2");
|
||||
std::vector<Slice> expected_keys = {"key_2", "key_3"};
|
||||
std::vector<Slice> expected_values = {"key_2_cf_2_val", "key_3_cf_3_val"};
|
||||
verifyCoalescingIterator(cfhs_order_0_1_2_3, expected_keys,
|
||||
VerifyCoalescingIterator(cfhs_order_0_1_2_3, expected_keys,
|
||||
expected_values, std::nullopt, &lb);
|
||||
}
|
||||
// with upper_bound
|
||||
@@ -321,7 +342,7 @@ TEST_F(CoalescingIteratorTest, LowerAndUpperBounds) {
|
||||
Slice ub = Slice("key_3");
|
||||
std::vector<Slice> expected_keys = {"key_1", "key_2"};
|
||||
std::vector<Slice> expected_values = {"key_1_cf_3_val", "key_2_cf_2_val"};
|
||||
verifyCoalescingIterator(cfhs_order_0_1_2_3, expected_keys,
|
||||
VerifyCoalescingIterator(cfhs_order_0_1_2_3, expected_keys,
|
||||
expected_values, std::nullopt, nullptr, &ub);
|
||||
}
|
||||
// with lower and upper bound
|
||||
@@ -330,7 +351,7 @@ TEST_F(CoalescingIteratorTest, LowerAndUpperBounds) {
|
||||
Slice ub = Slice("key_3");
|
||||
std::vector<Slice> expected_keys = {"key_2"};
|
||||
std::vector<Slice> expected_values = {"key_2_cf_2_val"};
|
||||
verifyCoalescingIterator(cfhs_order_0_1_2_3, expected_keys,
|
||||
VerifyCoalescingIterator(cfhs_order_0_1_2_3, expected_keys,
|
||||
expected_values, std::nullopt, &lb, &ub);
|
||||
}
|
||||
|
||||
@@ -342,7 +363,7 @@ TEST_F(CoalescingIteratorTest, LowerAndUpperBounds) {
|
||||
Slice lb = Slice("key_2");
|
||||
std::vector<Slice> expected_keys = {"key_2", "key_3"};
|
||||
std::vector<Slice> expected_values = {"key_2_cf_1_val", "key_3_cf_1_val"};
|
||||
verifyCoalescingIterator(cfhs_order_3_2_0_1, expected_keys,
|
||||
VerifyCoalescingIterator(cfhs_order_3_2_0_1, expected_keys,
|
||||
expected_values, std::nullopt, &lb);
|
||||
}
|
||||
// with upper_bound
|
||||
@@ -351,7 +372,7 @@ TEST_F(CoalescingIteratorTest, LowerAndUpperBounds) {
|
||||
Slice ub = Slice("key_3");
|
||||
std::vector<Slice> expected_keys = {"key_1", "key_2"};
|
||||
std::vector<Slice> expected_values = {"key_1_cf_0_val", "key_2_cf_1_val"};
|
||||
verifyCoalescingIterator(cfhs_order_3_2_0_1, expected_keys,
|
||||
VerifyCoalescingIterator(cfhs_order_3_2_0_1, expected_keys,
|
||||
expected_values, std::nullopt, nullptr, &ub);
|
||||
}
|
||||
// with lower and upper bound
|
||||
@@ -360,7 +381,7 @@ TEST_F(CoalescingIteratorTest, LowerAndUpperBounds) {
|
||||
Slice ub = Slice("key_3");
|
||||
std::vector<Slice> expected_keys = {"key_2"};
|
||||
std::vector<Slice> expected_values = {"key_2_cf_1_val"};
|
||||
verifyCoalescingIterator(cfhs_order_3_2_0_1, expected_keys,
|
||||
VerifyCoalescingIterator(cfhs_order_3_2_0_1, expected_keys,
|
||||
expected_values, std::nullopt, &lb, &ub);
|
||||
}
|
||||
{
|
||||
@@ -662,7 +683,7 @@ TEST_F(CoalescingIteratorTest, WideColumns) {
|
||||
key_2_expected_columns_cfh_order_1_2, key_3_expected_columns,
|
||||
key_4_expected_columns};
|
||||
|
||||
verifyCoalescingIterator(cfhs_order_0_1_2_3, expected_keys, expected_values,
|
||||
VerifyCoalescingIterator(cfhs_order_0_1_2_3, expected_keys, expected_values,
|
||||
expected_wide_columns_0_1_2_3);
|
||||
}
|
||||
|
||||
@@ -677,7 +698,7 @@ TEST_F(CoalescingIteratorTest, WideColumns) {
|
||||
key_2_expected_columns_cfh_order_2_1, key_3_expected_columns,
|
||||
key_4_expected_columns};
|
||||
|
||||
verifyCoalescingIterator(cfhs_order_3_2_0_1, expected_keys, expected_values,
|
||||
VerifyCoalescingIterator(cfhs_order_3_2_0_1, expected_keys, expected_values,
|
||||
expected_wide_columns_3_2_0_1);
|
||||
}
|
||||
}
|
||||
@@ -700,8 +721,8 @@ TEST_F(CoalescingIteratorTest, DifferentComparatorsInMultiCFs) {
|
||||
ASSERT_OK(Put(1, "key_2", "value_2"));
|
||||
ASSERT_OK(Put(1, "key_3", "value_3"));
|
||||
|
||||
verifyExpectedKeys(handles_[0], {"key_1", "key_2", "key_3"});
|
||||
verifyExpectedKeys(handles_[1], {"key_3", "key_2", "key_1"});
|
||||
VerifyExpectedKeys(handles_[0], {"key_1", "key_2", "key_3"});
|
||||
VerifyExpectedKeys(handles_[1], {"key_3", "key_2", "key_1"});
|
||||
|
||||
std::unique_ptr<Iterator> iter =
|
||||
db_->NewCoalescingIterator(ReadOptions(), handles_);
|
||||
@@ -742,10 +763,10 @@ TEST_F(CoalescingIteratorTest, CustomComparatorsInMultiCFs) {
|
||||
ASSERT_OK(Put(1, "key_003_005", "value_1_5"));
|
||||
ASSERT_OK(Put(1, "key_003_006", "value_1_4"));
|
||||
|
||||
verifyExpectedKeys(
|
||||
VerifyExpectedKeys(
|
||||
handles_[0], {"key_001_003", "key_001_002", "key_001_001", "key_002_003",
|
||||
"key_002_002", "key_002_001"});
|
||||
verifyExpectedKeys(
|
||||
VerifyExpectedKeys(
|
||||
handles_[1], {"key_001_003", "key_001_002", "key_001_001", "key_003_006",
|
||||
"key_003_005", "key_003_004"});
|
||||
|
||||
@@ -755,66 +776,205 @@ TEST_F(CoalescingIteratorTest, CustomComparatorsInMultiCFs) {
|
||||
std::vector<Slice> expected_values = {"value_1_1", "value_1_2", "value_1_3",
|
||||
"value_0_4", "value_0_5", "value_0_6",
|
||||
"value_1_4", "value_1_5", "value_1_6"};
|
||||
int i = 0;
|
||||
std::unique_ptr<Iterator> iter =
|
||||
db_->NewCoalescingIterator(ReadOptions(), handles_);
|
||||
|
||||
size_t i = 0;
|
||||
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
|
||||
ASSERT_EQ(expected_keys[i], iter->key());
|
||||
ASSERT_EQ(expected_values[i], iter->value());
|
||||
++i;
|
||||
}
|
||||
|
||||
ASSERT_EQ(expected_keys.size(), i);
|
||||
ASSERT_OK(iter->status());
|
||||
}
|
||||
|
||||
TEST_F(CoalescingIteratorTest, AllowUnpreparedValue) {
|
||||
Options options = GetDefaultOptions();
|
||||
options.enable_blob_files = true;
|
||||
|
||||
CreateAndReopenWithCF({"cf_1", "cf_2", "cf_3"}, options);
|
||||
|
||||
ASSERT_OK(Put(0, "key_1", "key_1_cf_0_val"));
|
||||
ASSERT_OK(Put(3, "key_1", "key_1_cf_3_val"));
|
||||
ASSERT_OK(Put(1, "key_2", "key_2_cf_1_val"));
|
||||
ASSERT_OK(Put(2, "key_2", "key_2_cf_2_val"));
|
||||
ASSERT_OK(Put(0, "key_3", "key_3_cf_0_val"));
|
||||
ASSERT_OK(Put(1, "key_3", "key_3_cf_1_val"));
|
||||
ASSERT_OK(Put(3, "key_3", "key_3_cf_3_val"));
|
||||
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
std::vector<ColumnFamilyHandle*> cfhs_order_3_2_0_1{handles_[3], handles_[2],
|
||||
handles_[0], handles_[1]};
|
||||
std::vector<Slice> expected_keys{"key_1", "key_2", "key_3"};
|
||||
std::vector<Slice> expected_values{"key_1_cf_0_val", "key_2_cf_1_val",
|
||||
"key_3_cf_1_val"};
|
||||
|
||||
VerifyCoalescingIterator(cfhs_order_3_2_0_1, expected_keys, expected_values,
|
||||
/* expected_wide_columns */ std::nullopt,
|
||||
/* lower_bound */ nullptr, /* upper_bound */ nullptr,
|
||||
/* allow_unprepared_value */ true);
|
||||
|
||||
ReadOptions read_options;
|
||||
read_options.allow_unprepared_value = true;
|
||||
|
||||
{
|
||||
std::unique_ptr<Iterator> iter =
|
||||
db_->NewCoalescingIterator(read_options, cfhs_order_3_2_0_1);
|
||||
iter->Seek("");
|
||||
ASSERT_EQ(IterStatus(iter.get()), "key_1->");
|
||||
ASSERT_TRUE(iter->PrepareValue());
|
||||
ASSERT_EQ(IterStatus(iter.get()), "key_1->key_1_cf_0_val");
|
||||
|
||||
iter->Seek("key_1");
|
||||
ASSERT_EQ(IterStatus(iter.get()), "key_1->");
|
||||
ASSERT_TRUE(iter->PrepareValue());
|
||||
ASSERT_EQ(IterStatus(iter.get()), "key_1->key_1_cf_0_val");
|
||||
|
||||
iter->Seek("key_2");
|
||||
ASSERT_EQ(IterStatus(iter.get()), "key_2->");
|
||||
ASSERT_TRUE(iter->PrepareValue());
|
||||
ASSERT_EQ(IterStatus(iter.get()), "key_2->key_2_cf_1_val");
|
||||
|
||||
iter->Next();
|
||||
ASSERT_EQ(IterStatus(iter.get()), "key_3->");
|
||||
ASSERT_TRUE(iter->PrepareValue());
|
||||
ASSERT_EQ(IterStatus(iter.get()), "key_3->key_3_cf_1_val");
|
||||
|
||||
iter->Seek("key_x");
|
||||
ASSERT_EQ(IterStatus(iter.get()), "(invalid)");
|
||||
}
|
||||
|
||||
{
|
||||
std::unique_ptr<Iterator> iter =
|
||||
db_->NewCoalescingIterator(read_options, cfhs_order_3_2_0_1);
|
||||
iter->SeekForPrev("");
|
||||
ASSERT_EQ(IterStatus(iter.get()), "(invalid)");
|
||||
|
||||
iter->SeekForPrev("key_1");
|
||||
ASSERT_EQ(IterStatus(iter.get()), "key_1->");
|
||||
ASSERT_TRUE(iter->PrepareValue());
|
||||
ASSERT_EQ(IterStatus(iter.get()), "key_1->key_1_cf_0_val");
|
||||
|
||||
iter->Next();
|
||||
ASSERT_EQ(IterStatus(iter.get()), "key_2->");
|
||||
ASSERT_TRUE(iter->PrepareValue());
|
||||
ASSERT_EQ(IterStatus(iter.get()), "key_2->key_2_cf_1_val");
|
||||
|
||||
iter->SeekForPrev("key_x");
|
||||
ASSERT_EQ(IterStatus(iter.get()), "key_3->");
|
||||
ASSERT_TRUE(iter->PrepareValue());
|
||||
ASSERT_EQ(IterStatus(iter.get()), "key_3->key_3_cf_1_val");
|
||||
|
||||
iter->Next();
|
||||
ASSERT_EQ(IterStatus(iter.get()), "(invalid)");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(CoalescingIteratorTest, AllowUnpreparedValue_Corruption) {
|
||||
Options options = GetDefaultOptions();
|
||||
options.enable_blob_files = true;
|
||||
|
||||
CreateAndReopenWithCF({"cf_1", "cf_2", "cf_3"}, options);
|
||||
|
||||
ASSERT_OK(Put(0, "key_1", "key_1_cf_0_val"));
|
||||
ASSERT_OK(Put(3, "key_1", "key_1_cf_3_val"));
|
||||
ASSERT_OK(Put(1, "key_2", "key_2_cf_1_val"));
|
||||
ASSERT_OK(Put(2, "key_2", "key_2_cf_2_val"));
|
||||
ASSERT_OK(Put(0, "key_3", "key_3_cf_0_val"));
|
||||
ASSERT_OK(Put(1, "key_3", "key_3_cf_1_val"));
|
||||
ASSERT_OK(Put(3, "key_3", "key_3_cf_3_val"));
|
||||
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
ReadOptions read_options;
|
||||
read_options.allow_unprepared_value = true;
|
||||
|
||||
std::vector<ColumnFamilyHandle*> cfhs_order_3_2_0_1{handles_[3], handles_[2],
|
||||
handles_[0], handles_[1]};
|
||||
|
||||
std::unique_ptr<Iterator> iter =
|
||||
db_->NewCoalescingIterator(read_options, cfhs_order_3_2_0_1);
|
||||
iter->SeekToFirst();
|
||||
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ(iter->key(), "key_1");
|
||||
ASSERT_TRUE(iter->value().empty());
|
||||
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"BlobFileReader::GetBlob:TamperWithResult", [](void* arg) {
|
||||
Slice* const blob_index = static_cast<Slice*>(arg);
|
||||
assert(blob_index);
|
||||
assert(!blob_index->empty());
|
||||
blob_index->remove_prefix(1);
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
ASSERT_FALSE(iter->PrepareValue());
|
||||
ASSERT_FALSE(iter->Valid());
|
||||
ASSERT_TRUE(iter->status().IsCorruption());
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
|
||||
class AttributeGroupIteratorTest : public DBTestBase {
|
||||
public:
|
||||
AttributeGroupIteratorTest()
|
||||
: DBTestBase("attribute_group_iterator_test", /*env_do_fsync=*/true) {}
|
||||
|
||||
void verifyAttributeGroupIterator(
|
||||
void VerifyAttributeGroupIterator(
|
||||
const std::vector<ColumnFamilyHandle*>& cfhs,
|
||||
const std::vector<Slice>& expected_keys,
|
||||
const std::vector<AttributeGroups>& expected_attribute_groups,
|
||||
const Slice* lower_bound = nullptr, const Slice* upper_bound = nullptr) {
|
||||
int i = 0;
|
||||
const std::vector<IteratorAttributeGroups>& expected_attribute_groups,
|
||||
const Slice* lower_bound = nullptr, const Slice* upper_bound = nullptr,
|
||||
bool allow_unprepared_value = false) {
|
||||
const size_t num_keys = expected_keys.size();
|
||||
|
||||
ReadOptions read_options;
|
||||
read_options.iterate_lower_bound = lower_bound;
|
||||
read_options.iterate_upper_bound = upper_bound;
|
||||
read_options.allow_unprepared_value = allow_unprepared_value;
|
||||
|
||||
std::unique_ptr<AttributeGroupIterator> iter =
|
||||
db_->NewAttributeGroupIterator(read_options, cfhs);
|
||||
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
|
||||
ASSERT_EQ(expected_keys[i], iter->key());
|
||||
auto iterator_attribute_groups = iter->attribute_groups();
|
||||
ASSERT_EQ(expected_attribute_groups[i].size(),
|
||||
iterator_attribute_groups.size());
|
||||
for (size_t cfh_i = 0; cfh_i < iterator_attribute_groups.size();
|
||||
cfh_i++) {
|
||||
ASSERT_EQ(expected_attribute_groups[i][cfh_i].column_family(),
|
||||
iterator_attribute_groups[cfh_i].column_family());
|
||||
ASSERT_EQ(expected_attribute_groups[i][cfh_i].columns(),
|
||||
iterator_attribute_groups[cfh_i].columns());
|
||||
}
|
||||
++i;
|
||||
}
|
||||
ASSERT_EQ(expected_keys.size(), i);
|
||||
ASSERT_OK(iter->status());
|
||||
|
||||
int rev_i = i - 1;
|
||||
for (iter->SeekToLast(); iter->Valid(); iter->Prev()) {
|
||||
ASSERT_EQ(expected_keys[rev_i], iter->key());
|
||||
auto iterator_attribute_groups = iter->attribute_groups();
|
||||
ASSERT_EQ(expected_attribute_groups[rev_i].size(),
|
||||
iterator_attribute_groups.size());
|
||||
for (size_t cfh_i = 0; cfh_i < iterator_attribute_groups.size();
|
||||
cfh_i++) {
|
||||
ASSERT_EQ(expected_attribute_groups[rev_i][cfh_i].column_family(),
|
||||
iterator_attribute_groups[cfh_i].column_family());
|
||||
ASSERT_EQ(expected_attribute_groups[rev_i][cfh_i].columns(),
|
||||
iterator_attribute_groups[cfh_i].columns());
|
||||
auto check_iter_entry = [&](size_t idx) {
|
||||
ASSERT_EQ(iter->key(), expected_keys[idx]);
|
||||
|
||||
if (allow_unprepared_value) {
|
||||
ASSERT_TRUE(iter->attribute_groups().empty());
|
||||
ASSERT_TRUE(iter->PrepareValue());
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
}
|
||||
rev_i--;
|
||||
|
||||
ASSERT_EQ(iter->attribute_groups(), expected_attribute_groups[idx]);
|
||||
};
|
||||
|
||||
{
|
||||
size_t i = 0;
|
||||
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
|
||||
check_iter_entry(i);
|
||||
++i;
|
||||
}
|
||||
|
||||
ASSERT_EQ(i, num_keys);
|
||||
ASSERT_OK(iter->status());
|
||||
}
|
||||
|
||||
{
|
||||
size_t i = 0;
|
||||
for (iter->SeekToLast(); iter->Valid(); iter->Prev()) {
|
||||
check_iter_entry(num_keys - 1 - i);
|
||||
++i;
|
||||
}
|
||||
|
||||
ASSERT_EQ(i, num_keys);
|
||||
ASSERT_OK(iter->status());
|
||||
}
|
||||
ASSERT_OK(iter->status());
|
||||
}
|
||||
};
|
||||
|
||||
@@ -870,45 +1030,140 @@ TEST_F(AttributeGroupIteratorTest, IterateAttributeGroups) {
|
||||
ASSERT_OK(db_->PutEntity(WriteOptions(), key_3, key_3_attribute_groups));
|
||||
ASSERT_OK(db_->PutEntity(WriteOptions(), key_4, key_4_attribute_groups));
|
||||
|
||||
IteratorAttributeGroups key_1_expected_attribute_groups{
|
||||
IteratorAttributeGroup(key_1_attribute_groups[0]),
|
||||
IteratorAttributeGroup(key_1_attribute_groups[1])};
|
||||
IteratorAttributeGroups key_2_expected_attribute_groups{
|
||||
IteratorAttributeGroup(key_2_attribute_groups[0]),
|
||||
IteratorAttributeGroup(key_2_attribute_groups[1])};
|
||||
IteratorAttributeGroups key_3_expected_attribute_groups{
|
||||
IteratorAttributeGroup(key_3_attribute_groups[0]),
|
||||
IteratorAttributeGroup(key_3_attribute_groups[1])};
|
||||
IteratorAttributeGroups key_4_expected_attribute_groups{
|
||||
IteratorAttributeGroup(key_4_attribute_groups[0]),
|
||||
IteratorAttributeGroup(key_4_attribute_groups[1])};
|
||||
|
||||
// Test for iteration over CF default->1->2->3
|
||||
std::vector<ColumnFamilyHandle*> cfhs_order_0_1_2_3 = {
|
||||
handles_[0], handles_[1], handles_[2], handles_[3]};
|
||||
{
|
||||
std::vector<Slice> expected_keys = {key_1, key_2, key_3, key_4};
|
||||
std::vector<AttributeGroups> expected_attribute_groups = {
|
||||
key_1_attribute_groups, key_2_attribute_groups, key_3_attribute_groups,
|
||||
key_4_attribute_groups};
|
||||
verifyAttributeGroupIterator(cfhs_order_0_1_2_3, expected_keys,
|
||||
std::vector<IteratorAttributeGroups> expected_attribute_groups{
|
||||
key_1_expected_attribute_groups, key_2_expected_attribute_groups,
|
||||
key_3_expected_attribute_groups, key_4_expected_attribute_groups};
|
||||
VerifyAttributeGroupIterator(cfhs_order_0_1_2_3, expected_keys,
|
||||
expected_attribute_groups);
|
||||
}
|
||||
|
||||
Slice lb = Slice("key_2");
|
||||
Slice ub = Slice("key_4");
|
||||
|
||||
// Test for lower bound only
|
||||
{
|
||||
std::vector<Slice> expected_keys = {key_2, key_3, key_4};
|
||||
std::vector<AttributeGroups> expected_attribute_groups = {
|
||||
key_2_attribute_groups, key_3_attribute_groups, key_4_attribute_groups};
|
||||
verifyAttributeGroupIterator(cfhs_order_0_1_2_3, expected_keys,
|
||||
std::vector<IteratorAttributeGroups> expected_attribute_groups{
|
||||
key_2_expected_attribute_groups, key_3_expected_attribute_groups,
|
||||
key_4_expected_attribute_groups};
|
||||
VerifyAttributeGroupIterator(cfhs_order_0_1_2_3, expected_keys,
|
||||
expected_attribute_groups, &lb);
|
||||
}
|
||||
|
||||
// Test for upper bound only
|
||||
{
|
||||
std::vector<Slice> expected_keys = {key_1, key_2, key_3};
|
||||
std::vector<AttributeGroups> expected_attribute_groups = {
|
||||
key_1_attribute_groups, key_2_attribute_groups, key_3_attribute_groups};
|
||||
verifyAttributeGroupIterator(cfhs_order_0_1_2_3, expected_keys,
|
||||
std::vector<IteratorAttributeGroups> expected_attribute_groups{
|
||||
key_1_expected_attribute_groups, key_2_expected_attribute_groups,
|
||||
key_3_expected_attribute_groups};
|
||||
VerifyAttributeGroupIterator(cfhs_order_0_1_2_3, expected_keys,
|
||||
expected_attribute_groups, nullptr, &ub);
|
||||
}
|
||||
|
||||
// Test for lower and upper bound
|
||||
{
|
||||
std::vector<Slice> expected_keys = {key_2, key_3};
|
||||
std::vector<AttributeGroups> expected_attribute_groups = {
|
||||
key_2_attribute_groups, key_3_attribute_groups};
|
||||
verifyAttributeGroupIterator(cfhs_order_0_1_2_3, expected_keys,
|
||||
std::vector<IteratorAttributeGroups> expected_attribute_groups{
|
||||
key_2_expected_attribute_groups, key_3_expected_attribute_groups};
|
||||
VerifyAttributeGroupIterator(cfhs_order_0_1_2_3, expected_keys,
|
||||
expected_attribute_groups, &lb, &ub);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(AttributeGroupIteratorTest, AllowUnpreparedValue) {
|
||||
Options options = GetDefaultOptions();
|
||||
CreateAndReopenWithCF({"cf_1", "cf_2", "cf_3"}, options);
|
||||
|
||||
constexpr char key_1[] = "key_1";
|
||||
WideColumns key_1_columns_in_cf_2{
|
||||
{kDefaultWideColumnName, "cf_2_col_val_0_key_1"},
|
||||
{"cf_2_col_name_1", "cf_2_col_val_1_key_1"},
|
||||
{"cf_2_col_name_2", "cf_2_col_val_2_key_1"}};
|
||||
WideColumns key_1_columns_in_cf_3{
|
||||
{"cf_3_col_name_1", "cf_3_col_val_1_key_1"},
|
||||
{"cf_3_col_name_2", "cf_3_col_val_2_key_1"},
|
||||
{"cf_3_col_name_3", "cf_3_col_val_3_key_1"}};
|
||||
|
||||
constexpr char key_2[] = "key_2";
|
||||
WideColumns key_2_columns_in_cf_1{
|
||||
{"cf_1_col_name_1", "cf_1_col_val_1_key_2"}};
|
||||
WideColumns key_2_columns_in_cf_2{
|
||||
{"cf_2_col_name_1", "cf_2_col_val_1_key_2"},
|
||||
{"cf_2_col_name_2", "cf_2_col_val_2_key_2"}};
|
||||
|
||||
constexpr char key_3[] = "key_3";
|
||||
WideColumns key_3_columns_in_cf_1{
|
||||
{"cf_1_col_name_1", "cf_1_col_val_1_key_3"}};
|
||||
WideColumns key_3_columns_in_cf_3{
|
||||
{"cf_3_col_name_1", "cf_3_col_val_1_key_3"}};
|
||||
|
||||
constexpr char key_4[] = "key_4";
|
||||
WideColumns key_4_columns_in_cf_0{
|
||||
{"cf_0_col_name_1", "cf_0_col_val_1_key_4"}};
|
||||
WideColumns key_4_columns_in_cf_2{
|
||||
{"cf_2_col_name_1", "cf_2_col_val_1_key_4"}};
|
||||
|
||||
AttributeGroups key_1_attribute_groups{
|
||||
AttributeGroup(handles_[2], key_1_columns_in_cf_2),
|
||||
AttributeGroup(handles_[3], key_1_columns_in_cf_3)};
|
||||
AttributeGroups key_2_attribute_groups{
|
||||
AttributeGroup(handles_[1], key_2_columns_in_cf_1),
|
||||
AttributeGroup(handles_[2], key_2_columns_in_cf_2)};
|
||||
AttributeGroups key_3_attribute_groups{
|
||||
AttributeGroup(handles_[1], key_3_columns_in_cf_1),
|
||||
AttributeGroup(handles_[3], key_3_columns_in_cf_3)};
|
||||
AttributeGroups key_4_attribute_groups{
|
||||
AttributeGroup(handles_[0], key_4_columns_in_cf_0),
|
||||
AttributeGroup(handles_[2], key_4_columns_in_cf_2)};
|
||||
|
||||
ASSERT_OK(db_->PutEntity(WriteOptions(), key_1, key_1_attribute_groups));
|
||||
ASSERT_OK(db_->PutEntity(WriteOptions(), key_2, key_2_attribute_groups));
|
||||
ASSERT_OK(db_->PutEntity(WriteOptions(), key_3, key_3_attribute_groups));
|
||||
ASSERT_OK(db_->PutEntity(WriteOptions(), key_4, key_4_attribute_groups));
|
||||
|
||||
IteratorAttributeGroups key_1_expected_attribute_groups{
|
||||
IteratorAttributeGroup(key_1_attribute_groups[0]),
|
||||
IteratorAttributeGroup(key_1_attribute_groups[1])};
|
||||
IteratorAttributeGroups key_2_expected_attribute_groups{
|
||||
IteratorAttributeGroup(key_2_attribute_groups[0]),
|
||||
IteratorAttributeGroup(key_2_attribute_groups[1])};
|
||||
IteratorAttributeGroups key_3_expected_attribute_groups{
|
||||
IteratorAttributeGroup(key_3_attribute_groups[0]),
|
||||
IteratorAttributeGroup(key_3_attribute_groups[1])};
|
||||
IteratorAttributeGroups key_4_expected_attribute_groups{
|
||||
IteratorAttributeGroup(key_4_attribute_groups[0]),
|
||||
IteratorAttributeGroup(key_4_attribute_groups[1])};
|
||||
|
||||
std::vector<ColumnFamilyHandle*> cfhs_order_0_1_2_3{handles_[0], handles_[1],
|
||||
handles_[2], handles_[3]};
|
||||
std::vector<Slice> expected_keys{key_1, key_2, key_3, key_4};
|
||||
std::vector<IteratorAttributeGroups> expected_attribute_groups{
|
||||
key_1_expected_attribute_groups, key_2_expected_attribute_groups,
|
||||
key_3_expected_attribute_groups, key_4_expected_attribute_groups};
|
||||
VerifyAttributeGroupIterator(
|
||||
cfhs_order_0_1_2_3, expected_keys, expected_attribute_groups,
|
||||
/* lower_bound */ nullptr, /* upper_bound */ nullptr,
|
||||
/* allow_unprepared_value */ true);
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -197,11 +197,10 @@ class FragmentedRangeTombstoneIterator : public InternalIterator {
|
||||
pinned_seq_pos_ = tombstones_->seq_end();
|
||||
}
|
||||
|
||||
RangeTombstone Tombstone(bool logical_strip_timestamp = false) const {
|
||||
RangeTombstone Tombstone() const {
|
||||
assert(Valid());
|
||||
if (icmp_->user_comparator()->timestamp_size()) {
|
||||
return RangeTombstone(start_key(), end_key(), seq(), timestamp(),
|
||||
logical_strip_timestamp);
|
||||
return RangeTombstone(start_key(), end_key(), seq(), timestamp());
|
||||
}
|
||||
return RangeTombstone(start_key(), end_key(), seq());
|
||||
}
|
||||
|
||||
+10
-9
@@ -105,6 +105,7 @@ class Repairer {
|
||||
SanitizeOptions(immutable_db_options_, default_cf_opts)),
|
||||
default_iopts_(
|
||||
ImmutableOptions(immutable_db_options_, default_cf_opts_)),
|
||||
default_mopts_(MutableCFOptions(default_cf_opts_)),
|
||||
unknown_cf_opts_(
|
||||
SanitizeOptions(immutable_db_options_, unknown_cf_opts)),
|
||||
create_unknown_cfs_(create_unknown_cfs),
|
||||
@@ -261,6 +262,7 @@ class Repairer {
|
||||
const InternalKeyComparator icmp_;
|
||||
const ColumnFamilyOptions default_cf_opts_;
|
||||
const ImmutableOptions default_iopts_; // table_cache_ holds reference
|
||||
const MutableCFOptions default_mopts_;
|
||||
const ColumnFamilyOptions unknown_cf_opts_;
|
||||
const bool create_unknown_cfs_;
|
||||
std::shared_ptr<Cache> raw_table_cache_;
|
||||
@@ -411,9 +413,12 @@ class Repairer {
|
||||
if (record_status.ok()) {
|
||||
const UnorderedMap<uint32_t, size_t>& record_ts_sz =
|
||||
reader.GetRecordedTimestampSize();
|
||||
// Use same value for `seq_per_batch` and `batch_per_txn` as
|
||||
// WriteBatchInternal::InsertInto does below.
|
||||
record_status = HandleWriteBatchTimestampSizeDifference(
|
||||
&batch, running_ts_sz, record_ts_sz,
|
||||
TimestampSizeConsistencyMode::kVerifyConsistency);
|
||||
TimestampSizeConsistencyMode::kVerifyConsistency,
|
||||
/* seq_per_batch */ false, /* batch_per_txn */ true);
|
||||
if (record_status.ok()) {
|
||||
record_status =
|
||||
WriteBatchInternal::InsertInto(&batch, cf_mems, nullptr, nullptr);
|
||||
@@ -537,8 +542,7 @@ class Repairer {
|
||||
// TODO: plumb Env::IOActivity, Env::IOPriority
|
||||
const ReadOptions read_options;
|
||||
status = table_cache_->GetTableProperties(
|
||||
file_options_, read_options, icmp_, t->meta, &props,
|
||||
0 /* block_protection_bytes_per_key */);
|
||||
file_options_, read_options, icmp_, t->meta, &props, default_mopts_);
|
||||
}
|
||||
if (status.ok()) {
|
||||
auto s =
|
||||
@@ -602,15 +606,13 @@ class Repairer {
|
||||
ropts.total_order_seek = true;
|
||||
InternalIterator* iter = table_cache_->NewIterator(
|
||||
ropts, file_options_, cfd->internal_comparator(), t->meta,
|
||||
nullptr /* range_del_agg */,
|
||||
cfd->GetLatestMutableCFOptions()->prefix_extractor,
|
||||
nullptr /* range_del_agg */, *cfd->GetLatestMutableCFOptions(),
|
||||
/*table_reader_ptr=*/nullptr, /*file_read_hist=*/nullptr,
|
||||
TableReaderCaller::kRepair, /*arena=*/nullptr, /*skip_filters=*/false,
|
||||
/*level=*/-1, /*max_file_size_for_l0_meta_pin=*/0,
|
||||
/*smallest_compaction_key=*/nullptr,
|
||||
/*largest_compaction_key=*/nullptr,
|
||||
/*allow_unprepared_value=*/false,
|
||||
cfd->GetLatestMutableCFOptions()->block_protection_bytes_per_key);
|
||||
/*allow_unprepared_value=*/false);
|
||||
ParsedInternalKey parsed;
|
||||
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
|
||||
Slice key = iter->key();
|
||||
@@ -651,8 +653,7 @@ class Repairer {
|
||||
std::unique_ptr<FragmentedRangeTombstoneIterator> r_iter;
|
||||
status = table_cache_->GetRangeTombstoneIterator(
|
||||
ropts, cfd->internal_comparator(), t->meta,
|
||||
cfd->GetLatestMutableCFOptions()->block_protection_bytes_per_key,
|
||||
&r_iter);
|
||||
*cfd->GetLatestMutableCFOptions(), &r_iter);
|
||||
|
||||
if (r_iter) {
|
||||
r_iter->SeekToFirst();
|
||||
|
||||
@@ -55,4 +55,11 @@ class WritePreparedSnapshotChecker : public SnapshotChecker {
|
||||
const WritePreparedTxnDB* const txn_db_;
|
||||
};
|
||||
|
||||
bool DataIsDefinitelyInSnapshot(SequenceNumber seqno, SequenceNumber snapshot,
|
||||
const SnapshotChecker* snapshot_checker);
|
||||
|
||||
bool DataIsDefinitelyNotInSnapshot(SequenceNumber seqno,
|
||||
SequenceNumber snapshot,
|
||||
const SnapshotChecker* snapshot_checker);
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
+54
-61
@@ -91,10 +91,9 @@ Status TableCache::GetTableReader(
|
||||
const ReadOptions& ro, const FileOptions& file_options,
|
||||
const InternalKeyComparator& internal_comparator,
|
||||
const FileMetaData& file_meta, bool sequential_mode,
|
||||
uint8_t block_protection_bytes_per_key, HistogramImpl* file_read_hist,
|
||||
std::unique_ptr<TableReader>* table_reader,
|
||||
const std::shared_ptr<const SliceTransform>& prefix_extractor,
|
||||
bool skip_filters, int level, bool prefetch_index_and_filter_in_cache,
|
||||
HistogramImpl* file_read_hist, std::unique_ptr<TableReader>* table_reader,
|
||||
const MutableCFOptions& mutable_cf_options, bool skip_filters, int level,
|
||||
bool prefetch_index_and_filter_in_cache,
|
||||
size_t max_file_size_for_l0_meta_pin, Temperature file_temperature) {
|
||||
std::string fname = TableFileName(
|
||||
ioptions_.cf_paths, file_meta.fd.GetNumber(), file_meta.fd.GetPathId());
|
||||
@@ -146,13 +145,14 @@ Status TableCache::GetTableReader(
|
||||
} else {
|
||||
expected_unique_id = kNullUniqueId64x2; // null ID == no verification
|
||||
}
|
||||
s = ioptions_.table_factory->NewTableReader(
|
||||
s = mutable_cf_options.table_factory->NewTableReader(
|
||||
ro,
|
||||
TableReaderOptions(
|
||||
ioptions_, prefix_extractor, file_options, internal_comparator,
|
||||
block_protection_bytes_per_key, skip_filters, immortal_tables_,
|
||||
false /* force_direct_prefetch */, level, block_cache_tracer_,
|
||||
max_file_size_for_l0_meta_pin, db_session_id_,
|
||||
ioptions_, mutable_cf_options.prefix_extractor, file_options,
|
||||
internal_comparator,
|
||||
mutable_cf_options.block_protection_bytes_per_key, skip_filters,
|
||||
immortal_tables_, false /* force_direct_prefetch */, level,
|
||||
block_cache_tracer_, max_file_size_for_l0_meta_pin, db_session_id_,
|
||||
file_meta.fd.GetNumber(), expected_unique_id,
|
||||
file_meta.fd.largest_seqno, file_meta.tail_size,
|
||||
file_meta.user_defined_timestamps_persisted),
|
||||
@@ -164,6 +164,7 @@ Status TableCache::GetTableReader(
|
||||
}
|
||||
|
||||
Cache::Handle* TableCache::Lookup(Cache* cache, uint64_t file_number) {
|
||||
// NOTE: sharing same Cache with BlobFileCache
|
||||
Slice key = GetSliceForFileNumber(&file_number);
|
||||
return cache->Lookup(key);
|
||||
}
|
||||
@@ -172,13 +173,13 @@ Status TableCache::FindTable(
|
||||
const ReadOptions& ro, const FileOptions& file_options,
|
||||
const InternalKeyComparator& internal_comparator,
|
||||
const FileMetaData& file_meta, TypedHandle** handle,
|
||||
uint8_t block_protection_bytes_per_key,
|
||||
const std::shared_ptr<const SliceTransform>& prefix_extractor,
|
||||
const bool no_io, HistogramImpl* file_read_hist, bool skip_filters,
|
||||
int level, bool prefetch_index_and_filter_in_cache,
|
||||
const MutableCFOptions& mutable_cf_options, const bool no_io,
|
||||
HistogramImpl* file_read_hist, bool skip_filters, int level,
|
||||
bool prefetch_index_and_filter_in_cache,
|
||||
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",
|
||||
@@ -197,9 +198,8 @@ Status TableCache::FindTable(
|
||||
|
||||
std::unique_ptr<TableReader> table_reader;
|
||||
Status s = GetTableReader(ro, file_options, internal_comparator, file_meta,
|
||||
false /* sequential mode */,
|
||||
block_protection_bytes_per_key, file_read_hist,
|
||||
&table_reader, prefix_extractor, skip_filters,
|
||||
false /* sequential mode */, file_read_hist,
|
||||
&table_reader, mutable_cf_options, skip_filters,
|
||||
level, prefetch_index_and_filter_in_cache,
|
||||
max_file_size_for_l0_meta_pin, file_temperature);
|
||||
if (!s.ok()) {
|
||||
@@ -223,13 +223,12 @@ InternalIterator* TableCache::NewIterator(
|
||||
const ReadOptions& options, const FileOptions& file_options,
|
||||
const InternalKeyComparator& icomparator, const FileMetaData& file_meta,
|
||||
RangeDelAggregator* range_del_agg,
|
||||
const std::shared_ptr<const SliceTransform>& prefix_extractor,
|
||||
TableReader** table_reader_ptr, HistogramImpl* file_read_hist,
|
||||
TableReaderCaller caller, Arena* arena, bool skip_filters, int level,
|
||||
size_t max_file_size_for_l0_meta_pin,
|
||||
const MutableCFOptions& mutable_cf_options, TableReader** table_reader_ptr,
|
||||
HistogramImpl* file_read_hist, TableReaderCaller caller, Arena* arena,
|
||||
bool skip_filters, int level, size_t max_file_size_for_l0_meta_pin,
|
||||
const InternalKey* smallest_compaction_key,
|
||||
const InternalKey* largest_compaction_key, bool allow_unprepared_value,
|
||||
uint8_t block_protection_bytes_per_key, const SequenceNumber* read_seqno,
|
||||
const SequenceNumber* read_seqno,
|
||||
std::unique_ptr<TruncatedRangeDelIterator>* range_del_iter) {
|
||||
PERF_TIMER_GUARD(new_table_iterator_nanos);
|
||||
|
||||
@@ -244,7 +243,7 @@ InternalIterator* TableCache::NewIterator(
|
||||
table_reader = fd.table_reader;
|
||||
if (table_reader == nullptr) {
|
||||
s = FindTable(options, file_options, icomparator, file_meta, &handle,
|
||||
block_protection_bytes_per_key, prefix_extractor,
|
||||
mutable_cf_options,
|
||||
options.read_tier == kBlockCacheTier /* no_io */,
|
||||
file_read_hist, skip_filters, level,
|
||||
true /* prefetch_index_and_filter_in_cache */,
|
||||
@@ -260,8 +259,9 @@ InternalIterator* TableCache::NewIterator(
|
||||
result = NewEmptyInternalIterator<Slice>(arena);
|
||||
} else {
|
||||
result = table_reader->NewIterator(
|
||||
options, prefix_extractor.get(), arena, skip_filters, caller,
|
||||
file_options.compaction_readahead_size, allow_unprepared_value);
|
||||
options, mutable_cf_options.prefix_extractor.get(), arena,
|
||||
skip_filters, caller, file_options.compaction_readahead_size,
|
||||
allow_unprepared_value);
|
||||
}
|
||||
if (handle != nullptr) {
|
||||
cache_.RegisterReleaseAsCleanup(handle, *result);
|
||||
@@ -328,7 +328,7 @@ InternalIterator* TableCache::NewIterator(
|
||||
Status TableCache::GetRangeTombstoneIterator(
|
||||
const ReadOptions& options,
|
||||
const InternalKeyComparator& internal_comparator,
|
||||
const FileMetaData& file_meta, uint8_t block_protection_bytes_per_key,
|
||||
const FileMetaData& file_meta, const MutableCFOptions& mutable_cf_options,
|
||||
std::unique_ptr<FragmentedRangeTombstoneIterator>* out_iter) {
|
||||
assert(out_iter);
|
||||
const FileDescriptor& fd = file_meta.fd;
|
||||
@@ -337,7 +337,7 @@ Status TableCache::GetRangeTombstoneIterator(
|
||||
TypedHandle* handle = nullptr;
|
||||
if (t == nullptr) {
|
||||
s = FindTable(options, file_options_, internal_comparator, file_meta,
|
||||
&handle, block_protection_bytes_per_key);
|
||||
&handle, mutable_cf_options);
|
||||
if (s.ok()) {
|
||||
t = cache_.Value(handle);
|
||||
}
|
||||
@@ -429,14 +429,13 @@ bool TableCache::GetFromRowCache(const Slice& user_key, IterKey& row_cache_key,
|
||||
return found;
|
||||
}
|
||||
|
||||
Status TableCache::Get(
|
||||
const ReadOptions& options,
|
||||
const InternalKeyComparator& internal_comparator,
|
||||
const FileMetaData& file_meta, const Slice& k, GetContext* get_context,
|
||||
uint8_t block_protection_bytes_per_key,
|
||||
const std::shared_ptr<const SliceTransform>& prefix_extractor,
|
||||
HistogramImpl* file_read_hist, bool skip_filters, int level,
|
||||
size_t max_file_size_for_l0_meta_pin) {
|
||||
Status TableCache::Get(const ReadOptions& options,
|
||||
const InternalKeyComparator& internal_comparator,
|
||||
const FileMetaData& file_meta, const Slice& k,
|
||||
GetContext* get_context,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
HistogramImpl* file_read_hist, bool skip_filters,
|
||||
int level, size_t max_file_size_for_l0_meta_pin) {
|
||||
auto& fd = file_meta.fd;
|
||||
std::string* row_cache_entry = nullptr;
|
||||
bool done = false;
|
||||
@@ -461,7 +460,7 @@ Status TableCache::Get(
|
||||
if (s.ok() && !done) {
|
||||
if (t == nullptr) {
|
||||
s = FindTable(options, file_options_, internal_comparator, file_meta,
|
||||
&handle, block_protection_bytes_per_key, prefix_extractor,
|
||||
&handle, mutable_cf_options,
|
||||
options.read_tier == kBlockCacheTier /* no_io */,
|
||||
file_read_hist, skip_filters, level,
|
||||
true /* prefetch_index_and_filter_in_cache */,
|
||||
@@ -490,7 +489,8 @@ Status TableCache::Get(
|
||||
}
|
||||
if (s.ok()) {
|
||||
get_context->SetReplayLog(row_cache_entry); // nullptr if no cache.
|
||||
s = t->Get(options, k, get_context, prefix_extractor.get(), skip_filters);
|
||||
s = t->Get(options, k, get_context,
|
||||
mutable_cf_options.prefix_extractor.get(), skip_filters);
|
||||
get_context->SetReplayLog(nullptr);
|
||||
} else if (options.read_tier == kBlockCacheTier && s.IsIncomplete()) {
|
||||
// Couldn't find table in cache and couldn't open it because of no_io.
|
||||
@@ -543,11 +543,9 @@ void TableCache::UpdateRangeTombstoneSeqnums(
|
||||
Status TableCache::MultiGetFilter(
|
||||
const ReadOptions& options,
|
||||
const InternalKeyComparator& internal_comparator,
|
||||
const FileMetaData& file_meta,
|
||||
const std::shared_ptr<const SliceTransform>& prefix_extractor,
|
||||
const FileMetaData& file_meta, const MutableCFOptions& mutable_cf_options,
|
||||
HistogramImpl* file_read_hist, int level,
|
||||
MultiGetContext::Range* mget_range, TypedHandle** table_handle,
|
||||
uint8_t block_protection_bytes_per_key) {
|
||||
MultiGetContext::Range* mget_range, TypedHandle** table_handle) {
|
||||
auto& fd = file_meta.fd;
|
||||
IterKey row_cache_key;
|
||||
std::string row_cache_entry_buffer;
|
||||
@@ -566,7 +564,7 @@ Status TableCache::MultiGetFilter(
|
||||
mget_range->end());
|
||||
if (t == nullptr) {
|
||||
s = FindTable(options, file_options_, internal_comparator, file_meta,
|
||||
&handle, block_protection_bytes_per_key, prefix_extractor,
|
||||
&handle, mutable_cf_options,
|
||||
options.read_tier == kBlockCacheTier /* no_io */,
|
||||
file_read_hist,
|
||||
/*skip_filters=*/false, level,
|
||||
@@ -578,7 +576,8 @@ Status TableCache::MultiGetFilter(
|
||||
*table_handle = handle;
|
||||
}
|
||||
if (s.ok()) {
|
||||
s = t->MultiGetFilter(options, prefix_extractor.get(), mget_range);
|
||||
s = t->MultiGetFilter(options, mutable_cf_options.prefix_extractor.get(),
|
||||
mget_range);
|
||||
}
|
||||
if (s.ok() && !options.ignore_range_deletions) {
|
||||
// Update the range tombstone sequence numbers for the keys here
|
||||
@@ -599,8 +598,7 @@ Status TableCache::GetTableProperties(
|
||||
const InternalKeyComparator& internal_comparator,
|
||||
const FileMetaData& file_meta,
|
||||
std::shared_ptr<const TableProperties>* properties,
|
||||
uint8_t block_protection_bytes_per_key,
|
||||
const std::shared_ptr<const SliceTransform>& prefix_extractor, bool no_io) {
|
||||
const MutableCFOptions& mutable_cf_options, bool no_io) {
|
||||
auto table_reader = file_meta.fd.table_reader;
|
||||
// table already been pre-loaded?
|
||||
if (table_reader) {
|
||||
@@ -611,8 +609,7 @@ Status TableCache::GetTableProperties(
|
||||
|
||||
TypedHandle* table_handle = nullptr;
|
||||
Status s = FindTable(read_options, file_options, internal_comparator,
|
||||
file_meta, &table_handle, block_protection_bytes_per_key,
|
||||
prefix_extractor, no_io);
|
||||
file_meta, &table_handle, mutable_cf_options, no_io);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -625,14 +622,15 @@ Status TableCache::GetTableProperties(
|
||||
|
||||
Status TableCache::ApproximateKeyAnchors(
|
||||
const ReadOptions& ro, const InternalKeyComparator& internal_comparator,
|
||||
const FileMetaData& file_meta, uint8_t block_protection_bytes_per_key,
|
||||
const FileMetaData& file_meta, const MutableCFOptions& mutable_cf_options,
|
||||
|
||||
std::vector<TableReader::Anchor>& anchors) {
|
||||
Status s;
|
||||
TableReader* t = file_meta.fd.table_reader;
|
||||
TypedHandle* handle = nullptr;
|
||||
if (t == nullptr) {
|
||||
s = FindTable(ro, file_options_, internal_comparator, file_meta, &handle,
|
||||
block_protection_bytes_per_key);
|
||||
mutable_cf_options);
|
||||
if (s.ok()) {
|
||||
t = cache_.Value(handle);
|
||||
}
|
||||
@@ -649,8 +647,7 @@ Status TableCache::ApproximateKeyAnchors(
|
||||
size_t TableCache::GetMemoryUsageByTableReader(
|
||||
const FileOptions& file_options, const ReadOptions& read_options,
|
||||
const InternalKeyComparator& internal_comparator,
|
||||
const FileMetaData& file_meta, uint8_t block_protection_bytes_per_key,
|
||||
const std::shared_ptr<const SliceTransform>& prefix_extractor) {
|
||||
const FileMetaData& file_meta, const MutableCFOptions& mutable_cf_options) {
|
||||
auto table_reader = file_meta.fd.table_reader;
|
||||
// table already been pre-loaded?
|
||||
if (table_reader) {
|
||||
@@ -658,9 +655,9 @@ size_t TableCache::GetMemoryUsageByTableReader(
|
||||
}
|
||||
|
||||
TypedHandle* table_handle = nullptr;
|
||||
Status s = FindTable(read_options, file_options, internal_comparator,
|
||||
file_meta, &table_handle, block_protection_bytes_per_key,
|
||||
prefix_extractor, true /* no_io */);
|
||||
Status s =
|
||||
FindTable(read_options, file_options, internal_comparator, file_meta,
|
||||
&table_handle, mutable_cf_options, true /* no_io */);
|
||||
if (!s.ok()) {
|
||||
return 0;
|
||||
}
|
||||
@@ -679,16 +676,14 @@ uint64_t TableCache::ApproximateOffsetOf(
|
||||
const ReadOptions& read_options, const Slice& key,
|
||||
const FileMetaData& file_meta, TableReaderCaller caller,
|
||||
const InternalKeyComparator& internal_comparator,
|
||||
uint8_t block_protection_bytes_per_key,
|
||||
const std::shared_ptr<const SliceTransform>& prefix_extractor) {
|
||||
const MutableCFOptions& mutable_cf_options) {
|
||||
uint64_t result = 0;
|
||||
TableReader* table_reader = file_meta.fd.table_reader;
|
||||
TypedHandle* table_handle = nullptr;
|
||||
if (table_reader == nullptr) {
|
||||
Status s =
|
||||
FindTable(read_options, file_options_, internal_comparator, file_meta,
|
||||
&table_handle, block_protection_bytes_per_key,
|
||||
prefix_extractor, false /* no_io */);
|
||||
&table_handle, mutable_cf_options, false /* no_io */);
|
||||
if (s.ok()) {
|
||||
table_reader = cache_.Value(table_handle);
|
||||
}
|
||||
@@ -708,16 +703,14 @@ uint64_t TableCache::ApproximateSize(
|
||||
const ReadOptions& read_options, const Slice& start, const Slice& end,
|
||||
const FileMetaData& file_meta, TableReaderCaller caller,
|
||||
const InternalKeyComparator& internal_comparator,
|
||||
uint8_t block_protection_bytes_per_key,
|
||||
const std::shared_ptr<const SliceTransform>& prefix_extractor) {
|
||||
const MutableCFOptions& mutable_cf_options) {
|
||||
uint64_t result = 0;
|
||||
TableReader* table_reader = file_meta.fd.table_reader;
|
||||
TypedHandle* table_handle = nullptr;
|
||||
if (table_reader == nullptr) {
|
||||
Status s =
|
||||
FindTable(read_options, file_options_, internal_comparator, file_meta,
|
||||
&table_handle, block_protection_bytes_per_key,
|
||||
prefix_extractor, false /* no_io */);
|
||||
&table_handle, mutable_cf_options, false /* no_io */);
|
||||
if (s.ok()) {
|
||||
table_reader = cache_.Value(table_handle);
|
||||
}
|
||||
|
||||
+65
-73
@@ -92,13 +92,12 @@ class TableCache {
|
||||
const ReadOptions& options, const FileOptions& toptions,
|
||||
const InternalKeyComparator& internal_comparator,
|
||||
const FileMetaData& file_meta, RangeDelAggregator* range_del_agg,
|
||||
const std::shared_ptr<const SliceTransform>& prefix_extractor,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
TableReader** table_reader_ptr, HistogramImpl* file_read_hist,
|
||||
TableReaderCaller caller, Arena* arena, bool skip_filters, int level,
|
||||
size_t max_file_size_for_l0_meta_pin,
|
||||
const InternalKey* smallest_compaction_key,
|
||||
const InternalKey* largest_compaction_key, bool allow_unprepared_value,
|
||||
uint8_t protection_bytes_per_key,
|
||||
const SequenceNumber* range_del_read_seqno = nullptr,
|
||||
std::unique_ptr<TruncatedRangeDelIterator>* range_del_iter = nullptr);
|
||||
|
||||
@@ -112,21 +111,20 @@ class TableCache {
|
||||
// recorded
|
||||
// @param skip_filters Disables loading/accessing the filter block
|
||||
// @param level The level this table is at, -1 for "not set / don't know"
|
||||
Status Get(
|
||||
const ReadOptions& options,
|
||||
const InternalKeyComparator& internal_comparator,
|
||||
const FileMetaData& file_meta, const Slice& k, GetContext* get_context,
|
||||
uint8_t block_protection_bytes_per_key,
|
||||
const std::shared_ptr<const SliceTransform>& prefix_extractor = nullptr,
|
||||
HistogramImpl* file_read_hist = nullptr, bool skip_filters = false,
|
||||
int level = -1, size_t max_file_size_for_l0_meta_pin = 0);
|
||||
Status Get(const ReadOptions& options,
|
||||
const InternalKeyComparator& internal_comparator,
|
||||
const FileMetaData& file_meta, const Slice& k,
|
||||
GetContext* get_context,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
HistogramImpl* file_read_hist = nullptr, bool skip_filters = false,
|
||||
int level = -1, size_t max_file_size_for_l0_meta_pin = 0);
|
||||
|
||||
// Return the range delete tombstone iterator of the file specified by
|
||||
// `file_meta`.
|
||||
Status GetRangeTombstoneIterator(
|
||||
const ReadOptions& options,
|
||||
const InternalKeyComparator& internal_comparator,
|
||||
const FileMetaData& file_meta, uint8_t block_protection_bytes_per_key,
|
||||
const FileMetaData& file_meta, const MutableCFOptions& mutable_cf_options,
|
||||
std::unique_ptr<FragmentedRangeTombstoneIterator>* out_iter);
|
||||
|
||||
// Call table reader's MultiGetFilter to use the bloom filter to filter out
|
||||
@@ -134,14 +132,13 @@ class TableCache {
|
||||
// If the table cache is looked up to get the table reader, the cache handle
|
||||
// is returned in table_handle. This handle should be passed back to
|
||||
// MultiGet() so it can be released.
|
||||
Status MultiGetFilter(
|
||||
const ReadOptions& options,
|
||||
const InternalKeyComparator& internal_comparator,
|
||||
const FileMetaData& file_meta,
|
||||
const std::shared_ptr<const SliceTransform>& prefix_extractor,
|
||||
HistogramImpl* file_read_hist, int level,
|
||||
MultiGetContext::Range* mget_range, TypedHandle** table_handle,
|
||||
uint8_t block_protection_bytes_per_key);
|
||||
Status MultiGetFilter(const ReadOptions& options,
|
||||
const InternalKeyComparator& internal_comparator,
|
||||
const FileMetaData& file_meta,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
HistogramImpl* file_read_hist, int level,
|
||||
MultiGetContext::Range* mget_range,
|
||||
TypedHandle** table_handle);
|
||||
|
||||
// If a seek to internal key "k" in specified file finds an entry,
|
||||
// call get_context->SaveValue() repeatedly until
|
||||
@@ -152,15 +149,15 @@ class TableCache {
|
||||
// in the embedded GetContext
|
||||
// @param skip_filters Disables loading/accessing the filter block
|
||||
// @param level The level this table is at, -1 for "not set / don't know"
|
||||
DECLARE_SYNC_AND_ASYNC(
|
||||
Status, MultiGet, const ReadOptions& options,
|
||||
const InternalKeyComparator& internal_comparator,
|
||||
const FileMetaData& file_meta, const MultiGetContext::Range* mget_range,
|
||||
uint8_t block_protection_bytes_per_key,
|
||||
const std::shared_ptr<const SliceTransform>& prefix_extractor = nullptr,
|
||||
HistogramImpl* file_read_hist = nullptr, bool skip_filters = false,
|
||||
bool skip_range_deletions = false, int level = -1,
|
||||
TypedHandle* table_handle = nullptr);
|
||||
DECLARE_SYNC_AND_ASYNC(Status, MultiGet, const ReadOptions& options,
|
||||
const InternalKeyComparator& internal_comparator,
|
||||
const FileMetaData& file_meta,
|
||||
const MultiGetContext::Range* mget_range,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
HistogramImpl* file_read_hist = nullptr,
|
||||
bool skip_filters = false,
|
||||
bool skip_range_deletions = false, int level = -1,
|
||||
TypedHandle* table_handle = nullptr);
|
||||
|
||||
// Evict any entry for the specified file number
|
||||
static void Evict(Cache* cache, uint64_t file_number);
|
||||
@@ -176,17 +173,16 @@ class TableCache {
|
||||
// Find table reader
|
||||
// @param skip_filters Disables loading/accessing the filter block
|
||||
// @param level == -1 means not specified
|
||||
Status FindTable(
|
||||
const ReadOptions& ro, const FileOptions& toptions,
|
||||
const InternalKeyComparator& internal_comparator,
|
||||
const FileMetaData& file_meta, TypedHandle**,
|
||||
uint8_t block_protection_bytes_per_key,
|
||||
const std::shared_ptr<const SliceTransform>& prefix_extractor = nullptr,
|
||||
const bool no_io = false, HistogramImpl* file_read_hist = nullptr,
|
||||
bool skip_filters = false, int level = -1,
|
||||
bool prefetch_index_and_filter_in_cache = true,
|
||||
size_t max_file_size_for_l0_meta_pin = 0,
|
||||
Temperature file_temperature = Temperature::kUnknown);
|
||||
Status FindTable(const ReadOptions& ro, const FileOptions& toptions,
|
||||
const InternalKeyComparator& internal_comparator,
|
||||
const FileMetaData& file_meta, TypedHandle**,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
const bool no_io = false,
|
||||
HistogramImpl* file_read_hist = nullptr,
|
||||
bool skip_filters = false, int level = -1,
|
||||
bool prefetch_index_and_filter_in_cache = true,
|
||||
size_t max_file_size_for_l0_meta_pin = 0,
|
||||
Temperature file_temperature = Temperature::kUnknown);
|
||||
|
||||
// Get the table properties of a given table.
|
||||
// @no_io: indicates if we should load table to the cache if it is not present
|
||||
@@ -194,19 +190,18 @@ class TableCache {
|
||||
// @returns: `properties` will be reset on success. Please note that we will
|
||||
// return Status::Incomplete() if table is not present in cache and
|
||||
// we set `no_io` to be true.
|
||||
Status GetTableProperties(
|
||||
const FileOptions& toptions, const ReadOptions& read_options,
|
||||
const InternalKeyComparator& internal_comparator,
|
||||
const FileMetaData& file_meta,
|
||||
std::shared_ptr<const TableProperties>* properties,
|
||||
uint8_t block_protection_bytes_per_key,
|
||||
const std::shared_ptr<const SliceTransform>& prefix_extractor = nullptr,
|
||||
bool no_io = false);
|
||||
Status GetTableProperties(const FileOptions& toptions,
|
||||
const ReadOptions& read_options,
|
||||
const InternalKeyComparator& internal_comparator,
|
||||
const FileMetaData& file_meta,
|
||||
std::shared_ptr<const TableProperties>* properties,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
bool no_io = false);
|
||||
|
||||
Status ApproximateKeyAnchors(const ReadOptions& ro,
|
||||
const InternalKeyComparator& internal_comparator,
|
||||
const FileMetaData& file_meta,
|
||||
uint8_t block_protection_bytes_per_key,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
std::vector<TableReader::Anchor>& anchors);
|
||||
|
||||
// Return total memory usage of the table reader of the file.
|
||||
@@ -214,25 +209,23 @@ class TableCache {
|
||||
size_t GetMemoryUsageByTableReader(
|
||||
const FileOptions& toptions, const ReadOptions& read_options,
|
||||
const InternalKeyComparator& internal_comparator,
|
||||
const FileMetaData& file_meta, uint8_t block_protection_bytes_per_key,
|
||||
const std::shared_ptr<const SliceTransform>& prefix_extractor = nullptr);
|
||||
const FileMetaData& file_meta,
|
||||
const MutableCFOptions& mutable_cf_options);
|
||||
|
||||
// Returns approximated offset of a key in a file represented by fd.
|
||||
uint64_t ApproximateOffsetOf(
|
||||
const ReadOptions& read_options, const Slice& key,
|
||||
const FileMetaData& file_meta, TableReaderCaller caller,
|
||||
const InternalKeyComparator& internal_comparator,
|
||||
uint8_t block_protection_bytes_per_key,
|
||||
const std::shared_ptr<const SliceTransform>& prefix_extractor = nullptr);
|
||||
uint64_t ApproximateOffsetOf(const ReadOptions& read_options,
|
||||
const Slice& key, const FileMetaData& file_meta,
|
||||
TableReaderCaller caller,
|
||||
const InternalKeyComparator& internal_comparator,
|
||||
const MutableCFOptions& mutable_cf_options);
|
||||
|
||||
// Returns approximated data size between start and end keys in a file
|
||||
// represented by fd (the start key must not be greater than the end key).
|
||||
uint64_t ApproximateSize(
|
||||
const ReadOptions& read_options, const Slice& start, const Slice& end,
|
||||
const FileMetaData& file_meta, TableReaderCaller caller,
|
||||
const InternalKeyComparator& internal_comparator,
|
||||
uint8_t block_protection_bytes_per_key,
|
||||
const std::shared_ptr<const SliceTransform>& prefix_extractor = nullptr);
|
||||
uint64_t ApproximateSize(const ReadOptions& read_options, const Slice& start,
|
||||
const Slice& end, const FileMetaData& file_meta,
|
||||
TableReaderCaller caller,
|
||||
const InternalKeyComparator& internal_comparator,
|
||||
const MutableCFOptions& mutable_cf_options);
|
||||
|
||||
CacheInterface& get_cache() { return cache_; }
|
||||
|
||||
@@ -250,17 +243,16 @@ class TableCache {
|
||||
|
||||
private:
|
||||
// Build a table reader
|
||||
Status GetTableReader(
|
||||
const ReadOptions& ro, const FileOptions& file_options,
|
||||
const InternalKeyComparator& internal_comparator,
|
||||
const FileMetaData& file_meta, bool sequential_mode,
|
||||
uint8_t block_protection_bytes_per_key, HistogramImpl* file_read_hist,
|
||||
std::unique_ptr<TableReader>* table_reader,
|
||||
const std::shared_ptr<const SliceTransform>& prefix_extractor = nullptr,
|
||||
bool skip_filters = false, int level = -1,
|
||||
bool prefetch_index_and_filter_in_cache = true,
|
||||
size_t max_file_size_for_l0_meta_pin = 0,
|
||||
Temperature file_temperature = Temperature::kUnknown);
|
||||
Status GetTableReader(const ReadOptions& ro, const FileOptions& file_options,
|
||||
const InternalKeyComparator& internal_comparator,
|
||||
const FileMetaData& file_meta, bool sequential_mode,
|
||||
HistogramImpl* file_read_hist,
|
||||
std::unique_ptr<TableReader>* table_reader,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
bool skip_filters = false, int level = -1,
|
||||
bool prefetch_index_and_filter_in_cache = true,
|
||||
size_t max_file_size_for_l0_meta_pin = 0,
|
||||
Temperature file_temperature = Temperature::kUnknown);
|
||||
|
||||
// Update the max_covering_tombstone_seq in the GetContext for each key based
|
||||
// on the range deletions in the table
|
||||
|
||||
@@ -17,10 +17,8 @@ namespace ROCKSDB_NAMESPACE {
|
||||
DEFINE_SYNC_AND_ASYNC(Status, TableCache::MultiGet)
|
||||
(const ReadOptions& options, const InternalKeyComparator& internal_comparator,
|
||||
const FileMetaData& file_meta, const MultiGetContext::Range* mget_range,
|
||||
uint8_t block_protection_bytes_per_key,
|
||||
const std::shared_ptr<const SliceTransform>& prefix_extractor,
|
||||
HistogramImpl* file_read_hist, bool skip_filters, bool skip_range_deletions,
|
||||
int level, TypedHandle* handle) {
|
||||
const MutableCFOptions& mutable_cf_options, HistogramImpl* file_read_hist,
|
||||
bool skip_filters, bool skip_range_deletions, int level, TypedHandle* handle) {
|
||||
auto& fd = file_meta.fd;
|
||||
Status s;
|
||||
TableReader* t = fd.table_reader;
|
||||
@@ -72,7 +70,7 @@ DEFINE_SYNC_AND_ASYNC(Status, TableCache::MultiGet)
|
||||
if (t == nullptr) {
|
||||
assert(handle == nullptr);
|
||||
s = FindTable(options, file_options_, internal_comparator, file_meta,
|
||||
&handle, block_protection_bytes_per_key, prefix_extractor,
|
||||
&handle, mutable_cf_options,
|
||||
options.read_tier == kBlockCacheTier /* no_io */,
|
||||
file_read_hist, skip_filters, level,
|
||||
true /* prefetch_index_and_filter_in_cache */,
|
||||
@@ -88,7 +86,8 @@ DEFINE_SYNC_AND_ASYNC(Status, TableCache::MultiGet)
|
||||
}
|
||||
if (s.ok()) {
|
||||
CO_AWAIT(t->MultiGet)
|
||||
(options, &table_range, prefix_extractor.get(), skip_filters);
|
||||
(options, &table_range, mutable_cf_options.prefix_extractor.get(),
|
||||
skip_filters);
|
||||
} else if (options.read_tier == kBlockCacheTier && s.IsIncomplete()) {
|
||||
for (auto iter = table_range.begin(); iter != table_range.end(); ++iter) {
|
||||
Status* status = iter->s;
|
||||
|
||||
+25
-26
@@ -24,6 +24,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "cache/cache_reservation_manager.h"
|
||||
#include "db/blob/blob_file_cache.h"
|
||||
#include "db/blob/blob_file_meta.h"
|
||||
#include "db/dbformat.h"
|
||||
#include "db/internal_stats.h"
|
||||
@@ -744,12 +745,9 @@ class VersionBuilder::Rep {
|
||||
return Status::Corruption("VersionBuilder", oss.str());
|
||||
}
|
||||
|
||||
// Note: we use C++11 for now but in C++14, this could be done in a more
|
||||
// elegant way using generalized lambda capture.
|
||||
VersionSet* const vs = version_set_;
|
||||
const ImmutableCFOptions* const ioptions = ioptions_;
|
||||
|
||||
auto deleter = [vs, ioptions](SharedBlobFileMetaData* shared_meta) {
|
||||
auto deleter = [vs = version_set_, ioptions = ioptions_,
|
||||
bc = cfd_ ? cfd_->blob_file_cache()
|
||||
: nullptr](SharedBlobFileMetaData* shared_meta) {
|
||||
if (vs) {
|
||||
assert(ioptions);
|
||||
assert(!ioptions->cf_paths.empty());
|
||||
@@ -758,6 +756,9 @@ class VersionBuilder::Rep {
|
||||
vs->AddObsoleteBlobFile(shared_meta->GetBlobFileNumber(),
|
||||
ioptions->cf_paths.front().path);
|
||||
}
|
||||
if (bc) {
|
||||
bc->Evict(shared_meta->GetBlobFileNumber());
|
||||
}
|
||||
|
||||
delete shared_meta;
|
||||
};
|
||||
@@ -766,7 +767,7 @@ class VersionBuilder::Rep {
|
||||
blob_file_number, blob_file_addition.GetTotalBlobCount(),
|
||||
blob_file_addition.GetTotalBlobBytes(),
|
||||
blob_file_addition.GetChecksumMethod(),
|
||||
blob_file_addition.GetChecksumValue(), deleter);
|
||||
blob_file_addition.GetChecksumValue(), std::move(deleter));
|
||||
|
||||
mutable_blob_file_metas_.emplace(
|
||||
blob_file_number, MutableBlobFileMetaData(std::move(shared_meta)));
|
||||
@@ -1636,12 +1637,12 @@ class VersionBuilder::Rep {
|
||||
return s;
|
||||
}
|
||||
|
||||
Status LoadTableHandlers(
|
||||
InternalStats* internal_stats, int max_threads,
|
||||
bool prefetch_index_and_filter_in_cache, bool is_initial_load,
|
||||
const std::shared_ptr<const SliceTransform>& prefix_extractor,
|
||||
size_t max_file_size_for_l0_meta_pin, const ReadOptions& read_options,
|
||||
uint8_t block_protection_bytes_per_key) {
|
||||
Status LoadTableHandlers(InternalStats* internal_stats, int max_threads,
|
||||
bool prefetch_index_and_filter_in_cache,
|
||||
bool is_initial_load,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
size_t max_file_size_for_l0_meta_pin,
|
||||
const ReadOptions& read_options) {
|
||||
assert(table_cache_ != nullptr);
|
||||
assert(!track_found_and_missing_files_ || valid_version_available_);
|
||||
|
||||
@@ -1716,7 +1717,7 @@ class VersionBuilder::Rep {
|
||||
statuses[file_idx] = table_cache_->FindTable(
|
||||
read_options, file_options_,
|
||||
*(base_vstorage_->InternalComparator()), *file_meta, &handle,
|
||||
block_protection_bytes_per_key, prefix_extractor, false /*no_io */,
|
||||
mutable_cf_options, false /*no_io */,
|
||||
internal_stats->GetFileReadHist(level), false, level,
|
||||
prefetch_index_and_filter_in_cache, max_file_size_for_l0_meta_pin,
|
||||
file_meta->temperature);
|
||||
@@ -1777,13 +1778,12 @@ Status VersionBuilder::SaveTo(VersionStorageInfo* vstorage) const {
|
||||
Status VersionBuilder::LoadTableHandlers(
|
||||
InternalStats* internal_stats, int max_threads,
|
||||
bool prefetch_index_and_filter_in_cache, bool is_initial_load,
|
||||
const std::shared_ptr<const SliceTransform>& prefix_extractor,
|
||||
size_t max_file_size_for_l0_meta_pin, const ReadOptions& read_options,
|
||||
uint8_t block_protection_bytes_per_key) {
|
||||
return rep_->LoadTableHandlers(
|
||||
internal_stats, max_threads, prefetch_index_and_filter_in_cache,
|
||||
is_initial_load, prefix_extractor, max_file_size_for_l0_meta_pin,
|
||||
read_options, block_protection_bytes_per_key);
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
size_t max_file_size_for_l0_meta_pin, const ReadOptions& read_options) {
|
||||
return rep_->LoadTableHandlers(internal_stats, max_threads,
|
||||
prefetch_index_and_filter_in_cache,
|
||||
is_initial_load, mutable_cf_options,
|
||||
max_file_size_for_l0_meta_pin, read_options);
|
||||
}
|
||||
|
||||
void VersionBuilder::CreateOrReplaceSavePoint() {
|
||||
@@ -1814,16 +1814,15 @@ Status VersionBuilder::SaveSavePointTo(VersionStorageInfo* vstorage) const {
|
||||
Status VersionBuilder::LoadSavePointTableHandlers(
|
||||
InternalStats* internal_stats, int max_threads,
|
||||
bool prefetch_index_and_filter_in_cache, bool is_initial_load,
|
||||
const std::shared_ptr<const SliceTransform>& prefix_extractor,
|
||||
size_t max_file_size_for_l0_meta_pin, const ReadOptions& read_options,
|
||||
uint8_t block_protection_bytes_per_key) {
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
size_t max_file_size_for_l0_meta_pin, const ReadOptions& read_options) {
|
||||
if (!savepoint_ || !savepoint_->ValidVersionAvailable()) {
|
||||
return Status::InvalidArgument();
|
||||
}
|
||||
return savepoint_->LoadTableHandlers(
|
||||
internal_stats, max_threads, prefetch_index_and_filter_in_cache,
|
||||
is_initial_load, prefix_extractor, max_file_size_for_l0_meta_pin,
|
||||
read_options, block_protection_bytes_per_key);
|
||||
is_initial_load, mutable_cf_options, max_file_size_for_l0_meta_pin,
|
||||
read_options);
|
||||
}
|
||||
|
||||
void VersionBuilder::ClearSavePoint() { savepoint_.reset(nullptr); }
|
||||
|
||||
+13
-12
@@ -54,12 +54,12 @@ class VersionBuilder {
|
||||
Status SaveTo(VersionStorageInfo* vstorage) const;
|
||||
|
||||
// Load all the table handlers for the current Version in the builder.
|
||||
Status LoadTableHandlers(
|
||||
InternalStats* internal_stats, int max_threads,
|
||||
bool prefetch_index_and_filter_in_cache, bool is_initial_load,
|
||||
const std::shared_ptr<const SliceTransform>& prefix_extractor,
|
||||
size_t max_file_size_for_l0_meta_pin, const ReadOptions& read_options,
|
||||
uint8_t block_protection_bytes_per_key);
|
||||
Status LoadTableHandlers(InternalStats* internal_stats, int max_threads,
|
||||
bool prefetch_index_and_filter_in_cache,
|
||||
bool is_initial_load,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
size_t max_file_size_for_l0_meta_pin,
|
||||
const ReadOptions& read_options);
|
||||
|
||||
//============APIs only used by VersionEditHandlerPointInTime ============//
|
||||
|
||||
@@ -99,12 +99,13 @@ class VersionBuilder {
|
||||
|
||||
// Load all the table handlers for the Version in the save point.
|
||||
// Non-OK status will be returned if there is not a valid save point.
|
||||
Status LoadSavePointTableHandlers(
|
||||
InternalStats* internal_stats, int max_threads,
|
||||
bool prefetch_index_and_filter_in_cache, bool is_initial_load,
|
||||
const std::shared_ptr<const SliceTransform>& prefix_extractor,
|
||||
size_t max_file_size_for_l0_meta_pin, const ReadOptions& read_options,
|
||||
uint8_t block_protection_bytes_per_key);
|
||||
Status LoadSavePointTableHandlers(InternalStats* internal_stats,
|
||||
int max_threads,
|
||||
bool prefetch_index_and_filter_in_cache,
|
||||
bool is_initial_load,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
size_t max_file_size_for_l0_meta_pin,
|
||||
const ReadOptions& read_options);
|
||||
|
||||
void ClearSavePoint();
|
||||
|
||||
|
||||
@@ -350,6 +350,14 @@ struct FileMetaData {
|
||||
file_checksum_func_name.size();
|
||||
return usage;
|
||||
}
|
||||
|
||||
// Returns whether this file is one with just one range tombstone. These type
|
||||
// of file should always be marked for compaction.
|
||||
bool FileIsStandAloneRangeTombstone() const {
|
||||
bool res = num_range_deletions == 1 && num_entries == num_range_deletions;
|
||||
assert(!res || fd.smallest_seqno == fd.largest_seqno);
|
||||
return res;
|
||||
}
|
||||
};
|
||||
|
||||
// A compressed copy of file meta data that just contain minimum data needed
|
||||
|
||||
@@ -545,9 +545,8 @@ Status VersionEditHandler::LoadTables(ColumnFamilyData* cfd,
|
||||
Status s = builder->LoadTableHandlers(
|
||||
cfd->internal_stats(),
|
||||
version_set_->db_options_->max_file_opening_threads,
|
||||
prefetch_index_and_filter_in_cache, is_initial_load,
|
||||
moptions->prefix_extractor, MaxFileSizeForL0MetaPin(*moptions),
|
||||
read_options_, moptions->block_protection_bytes_per_key);
|
||||
prefetch_index_and_filter_in_cache, is_initial_load, *moptions,
|
||||
MaxFileSizeForL0MetaPin(*moptions), read_options_);
|
||||
if ((s.IsPathNotFound() || s.IsCorruption()) && no_error_if_files_missing_) {
|
||||
s = Status::OK();
|
||||
}
|
||||
@@ -870,8 +869,7 @@ Status VersionEditHandlerPointInTime::MaybeCreateVersionBeforeApplyEdit(
|
||||
s = builder->LoadSavePointTableHandlers(
|
||||
cfd->internal_stats(),
|
||||
version_set_->db_options_->max_file_opening_threads, false, true,
|
||||
cf_opts_ptr->prefix_extractor, MaxFileSizeForL0MetaPin(*cf_opts_ptr),
|
||||
read_options_, cf_opts_ptr->block_protection_bytes_per_key);
|
||||
*cf_opts_ptr, MaxFileSizeForL0MetaPin(*cf_opts_ptr), read_options_);
|
||||
if (!s.ok()) {
|
||||
delete version;
|
||||
if (s.IsCorruption()) {
|
||||
|
||||
+77
-76
@@ -964,15 +964,15 @@ namespace {
|
||||
|
||||
class LevelIterator final : public InternalIterator {
|
||||
public:
|
||||
// @param read_options Must outlive this iterator.
|
||||
// NOTE: many of the const& parameters are saved in this object (so
|
||||
// must outlive this object)
|
||||
LevelIterator(
|
||||
TableCache* table_cache, const ReadOptions& read_options,
|
||||
const FileOptions& file_options, const InternalKeyComparator& icomparator,
|
||||
const LevelFilesBrief* flevel,
|
||||
const std::shared_ptr<const SliceTransform>& prefix_extractor,
|
||||
const LevelFilesBrief* flevel, const MutableCFOptions& mutable_cf_options,
|
||||
bool should_sample, HistogramImpl* file_read_hist,
|
||||
TableReaderCaller caller, bool skip_filters, int level,
|
||||
uint8_t block_protection_bytes_per_key, RangeDelAggregator* range_del_agg,
|
||||
RangeDelAggregator* range_del_agg,
|
||||
const std::vector<AtomicCompactionUnitBoundary>* compaction_boundaries =
|
||||
nullptr,
|
||||
bool allow_unprepared_value = false,
|
||||
@@ -984,7 +984,8 @@ class LevelIterator final : public InternalIterator {
|
||||
icomparator_(icomparator),
|
||||
user_comparator_(icomparator.user_comparator()),
|
||||
flevel_(flevel),
|
||||
prefix_extractor_(prefix_extractor),
|
||||
mutable_cf_options_(mutable_cf_options),
|
||||
prefix_extractor_(mutable_cf_options.prefix_extractor.get()),
|
||||
file_read_hist_(file_read_hist),
|
||||
caller_(caller),
|
||||
file_index_(flevel_->num_files),
|
||||
@@ -996,7 +997,6 @@ class LevelIterator final : public InternalIterator {
|
||||
? read_options.snapshot->GetSequenceNumber()
|
||||
: kMaxSequenceNumber),
|
||||
level_(level),
|
||||
block_protection_bytes_per_key_(block_protection_bytes_per_key),
|
||||
should_sample_(should_sample),
|
||||
skip_filters_(skip_filters),
|
||||
allow_unprepared_value_(allow_unprepared_value),
|
||||
@@ -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();
|
||||
}
|
||||
@@ -1142,12 +1147,12 @@ class LevelIterator final : public InternalIterator {
|
||||
ClearRangeTombstoneIter();
|
||||
return table_cache_->NewIterator(
|
||||
read_options_, file_options_, icomparator_, *file_meta.file_metadata,
|
||||
range_del_agg_, prefix_extractor_,
|
||||
range_del_agg_, mutable_cf_options_,
|
||||
nullptr /* don't need reference to table */, file_read_hist_, caller_,
|
||||
/*arena=*/nullptr, skip_filters_, level_,
|
||||
/*max_file_size_for_l0_meta_pin=*/0, smallest_compaction_key,
|
||||
largest_compaction_key, allow_unprepared_value_,
|
||||
block_protection_bytes_per_key_, &read_seq_, range_tombstone_iter_);
|
||||
largest_compaction_key, allow_unprepared_value_, &read_seq_,
|
||||
range_tombstone_iter_);
|
||||
}
|
||||
|
||||
// Check if current file being fully within iterate_lower_bound.
|
||||
@@ -1171,10 +1176,8 @@ class LevelIterator final : public InternalIterator {
|
||||
const UserComparatorWrapper user_comparator_;
|
||||
const LevelFilesBrief* flevel_;
|
||||
mutable FileDescriptor current_value_;
|
||||
// `prefix_extractor_` may be non-null even for total order seek. Checking
|
||||
// this variable is not the right way to identify whether prefix iterator
|
||||
// is used.
|
||||
const std::shared_ptr<const SliceTransform>& prefix_extractor_;
|
||||
const MutableCFOptions& mutable_cf_options_;
|
||||
const SliceTransform* prefix_extractor_;
|
||||
|
||||
HistogramImpl* file_read_hist_;
|
||||
TableReaderCaller caller_;
|
||||
@@ -1208,7 +1211,6 @@ class LevelIterator final : public InternalIterator {
|
||||
SequenceNumber read_seq_;
|
||||
|
||||
int level_;
|
||||
uint8_t block_protection_bytes_per_key_;
|
||||
bool should_sample_;
|
||||
bool skip_filters_;
|
||||
bool allow_unprepared_value_;
|
||||
@@ -1575,8 +1577,7 @@ Status Version::GetTableProperties(const ReadOptions& read_options,
|
||||
auto ioptions = cfd_->ioptions();
|
||||
Status s = table_cache->GetTableProperties(
|
||||
file_options_, read_options, cfd_->internal_comparator(), *file_meta, tp,
|
||||
mutable_cf_options_.block_protection_bytes_per_key,
|
||||
mutable_cf_options_.prefix_extractor, true /* no io */);
|
||||
mutable_cf_options_, true /* no io */);
|
||||
if (s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -1662,8 +1663,7 @@ Status Version::TablesRangeTombstoneSummary(int max_entries_to_print,
|
||||
|
||||
Status s = table_cache->GetRangeTombstoneIterator(
|
||||
read_options, cfd_->internal_comparator(), *file_meta,
|
||||
cfd_->GetLatestMutableCFOptions()->block_protection_bytes_per_key,
|
||||
&tombstone_iter);
|
||||
mutable_cf_options_, &tombstone_iter);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -1780,9 +1780,7 @@ size_t Version::GetMemoryUsageByTableReaders(const ReadOptions& read_options) {
|
||||
for (size_t i = 0; i < file_level.num_files; i++) {
|
||||
total_usage += cfd_->table_cache()->GetMemoryUsageByTableReader(
|
||||
file_options_, read_options, cfd_->internal_comparator(),
|
||||
*file_level.files[i].file_metadata,
|
||||
mutable_cf_options_.block_protection_bytes_per_key,
|
||||
mutable_cf_options_.prefix_extractor);
|
||||
*file_level.files[i].file_metadata, mutable_cf_options_);
|
||||
}
|
||||
}
|
||||
return total_usage;
|
||||
@@ -1931,10 +1929,9 @@ InternalIterator* Version::TEST_GetLevelIterator(
|
||||
auto level_iter = new (mem) LevelIterator(
|
||||
cfd_->table_cache(), read_options, file_options_,
|
||||
cfd_->internal_comparator(), &storage_info_.LevelFilesBrief(level),
|
||||
mutable_cf_options_.prefix_extractor, should_sample_file_read(),
|
||||
mutable_cf_options_, should_sample_file_read(),
|
||||
cfd_->internal_stats()->GetFileReadHist(level),
|
||||
TableReaderCaller::kUserIterator, IsFilterSkipped(level), level,
|
||||
mutable_cf_options_.block_protection_bytes_per_key,
|
||||
nullptr /* range_del_agg */, nullptr /* compaction_boundaries */,
|
||||
allow_unprepared_value, &tombstone_iter_ptr);
|
||||
if (read_options.ignore_range_deletions) {
|
||||
@@ -1968,9 +1965,16 @@ uint64_t VersionStorageInfo::GetEstimatedActiveKeys() const {
|
||||
}
|
||||
|
||||
if (current_num_samples_ < file_count) {
|
||||
// casting to avoid overflowing
|
||||
return static_cast<uint64_t>(
|
||||
(est * static_cast<double>(file_count) / current_num_samples_));
|
||||
assert(current_num_samples_ != 0);
|
||||
assert(est != 0);
|
||||
double multiplier = static_cast<double>(file_count) / current_num_samples_;
|
||||
double maximum_multiplier =
|
||||
static_cast<double>(std::numeric_limits<uint64_t>::max()) / est;
|
||||
// If it can overflow, we return the maximum unsigned long.
|
||||
if (multiplier >= maximum_multiplier) {
|
||||
return std::numeric_limits<uint64_t>::max();
|
||||
}
|
||||
return static_cast<uint64_t>(est * multiplier);
|
||||
} else {
|
||||
return est;
|
||||
}
|
||||
@@ -2032,14 +2036,12 @@ void Version::AddIteratorsForLevel(const ReadOptions& read_options,
|
||||
const auto& file = storage_info_.LevelFilesBrief(0).files[i];
|
||||
auto table_iter = cfd_->table_cache()->NewIterator(
|
||||
read_options, soptions, cfd_->internal_comparator(),
|
||||
*file.file_metadata, /*range_del_agg=*/nullptr,
|
||||
mutable_cf_options_.prefix_extractor, nullptr,
|
||||
cfd_->internal_stats()->GetFileReadHist(0),
|
||||
*file.file_metadata, /*range_del_agg=*/nullptr, mutable_cf_options_,
|
||||
nullptr, cfd_->internal_stats()->GetFileReadHist(0),
|
||||
TableReaderCaller::kUserIterator, arena,
|
||||
/*skip_filters=*/false, /*level=*/0, max_file_size_for_l0_meta_pin_,
|
||||
/*smallest_compaction_key=*/nullptr,
|
||||
/*largest_compaction_key=*/nullptr, allow_unprepared_value,
|
||||
mutable_cf_options_.block_protection_bytes_per_key,
|
||||
/*range_del_read_seqno=*/nullptr, &tombstone_iter);
|
||||
if (read_options.ignore_range_deletions) {
|
||||
merge_iter_builder->AddIterator(table_iter);
|
||||
@@ -2066,10 +2068,9 @@ void Version::AddIteratorsForLevel(const ReadOptions& read_options,
|
||||
auto level_iter = new (mem) LevelIterator(
|
||||
cfd_->table_cache(), read_options, soptions,
|
||||
cfd_->internal_comparator(), &storage_info_.LevelFilesBrief(level),
|
||||
mutable_cf_options_.prefix_extractor, should_sample_file_read(),
|
||||
mutable_cf_options_, should_sample_file_read(),
|
||||
cfd_->internal_stats()->GetFileReadHist(level),
|
||||
TableReaderCaller::kUserIterator, IsFilterSkipped(level), level,
|
||||
mutable_cf_options_.block_protection_bytes_per_key,
|
||||
/*range_del_agg=*/nullptr,
|
||||
/*compaction_boundaries=*/nullptr, allow_unprepared_value,
|
||||
&tombstone_iter_ptr);
|
||||
@@ -2108,15 +2109,13 @@ Status Version::OverlapWithLevelIterator(const ReadOptions& read_options,
|
||||
}
|
||||
ScopedArenaPtr<InternalIterator> iter(cfd_->table_cache()->NewIterator(
|
||||
read_options, file_options, cfd_->internal_comparator(),
|
||||
*file->file_metadata, &range_del_agg,
|
||||
mutable_cf_options_.prefix_extractor, nullptr,
|
||||
*file->file_metadata, &range_del_agg, mutable_cf_options_, nullptr,
|
||||
cfd_->internal_stats()->GetFileReadHist(0),
|
||||
TableReaderCaller::kUserIterator, &arena,
|
||||
/*skip_filters=*/false, /*level=*/0, max_file_size_for_l0_meta_pin_,
|
||||
/*smallest_compaction_key=*/nullptr,
|
||||
/*largest_compaction_key=*/nullptr,
|
||||
/*allow_unprepared_value=*/false,
|
||||
mutable_cf_options_.block_protection_bytes_per_key));
|
||||
/*allow_unprepared_value=*/false));
|
||||
status = OverlapWithIterator(ucmp, smallest_user_key, largest_user_key,
|
||||
iter.get(), overlap);
|
||||
if (!status.ok() || *overlap) {
|
||||
@@ -2128,11 +2127,10 @@ Status Version::OverlapWithLevelIterator(const ReadOptions& read_options,
|
||||
ScopedArenaPtr<InternalIterator> iter(new (mem) LevelIterator(
|
||||
cfd_->table_cache(), read_options, file_options,
|
||||
cfd_->internal_comparator(), &storage_info_.LevelFilesBrief(level),
|
||||
mutable_cf_options_.prefix_extractor, should_sample_file_read(),
|
||||
mutable_cf_options_, should_sample_file_read(),
|
||||
cfd_->internal_stats()->GetFileReadHist(level),
|
||||
TableReaderCaller::kUserIterator, IsFilterSkipped(level), level,
|
||||
mutable_cf_options_.block_protection_bytes_per_key, &range_del_agg,
|
||||
nullptr, false));
|
||||
&range_del_agg, nullptr, false));
|
||||
status = OverlapWithIterator(ucmp, smallest_user_key, largest_user_key,
|
||||
iter.get(), overlap);
|
||||
}
|
||||
@@ -2445,8 +2443,7 @@ void Version::Get(const ReadOptions& read_options, const LookupKey& k,
|
||||
StopWatchNano timer(clock_, timer_enabled /* auto_start */);
|
||||
*status = table_cache_->Get(
|
||||
read_options, *internal_comparator(), *f->file_metadata, ikey,
|
||||
&get_context, mutable_cf_options_.block_protection_bytes_per_key,
|
||||
mutable_cf_options_.prefix_extractor,
|
||||
&get_context, mutable_cf_options_,
|
||||
cfd_->internal_stats()->GetFileReadHist(fp.GetHitFileLevel()),
|
||||
IsFilterSkipped(static_cast<int>(fp.GetHitFileLevel()),
|
||||
fp.IsHitFileLastInLevel()),
|
||||
@@ -2681,10 +2678,9 @@ void Version::MultiGet(const ReadOptions& read_options, MultiGetRange* range,
|
||||
if (!skip_filters) {
|
||||
Status status = table_cache_->MultiGetFilter(
|
||||
read_options, *internal_comparator(), *f->file_metadata,
|
||||
mutable_cf_options_.prefix_extractor,
|
||||
mutable_cf_options_,
|
||||
cfd_->internal_stats()->GetFileReadHist(fp.GetHitFileLevel()),
|
||||
fp.GetHitFileLevel(), &file_range, &table_handle,
|
||||
mutable_cf_options_.block_protection_bytes_per_key);
|
||||
fp.GetHitFileLevel(), &file_range, &table_handle);
|
||||
skip_range_deletions = true;
|
||||
if (status.ok()) {
|
||||
skip_filters = true;
|
||||
@@ -2868,10 +2864,9 @@ Status Version::ProcessBatch(
|
||||
if (!skip_filters) {
|
||||
Status status = table_cache_->MultiGetFilter(
|
||||
read_options, *internal_comparator(), *f->file_metadata,
|
||||
mutable_cf_options_.prefix_extractor,
|
||||
mutable_cf_options_,
|
||||
cfd_->internal_stats()->GetFileReadHist(fp.GetHitFileLevel()),
|
||||
fp.GetHitFileLevel(), &file_range, &table_handle,
|
||||
mutable_cf_options_.block_protection_bytes_per_key);
|
||||
fp.GetHitFileLevel(), &file_range, &table_handle);
|
||||
if (status.ok()) {
|
||||
skip_filters = true;
|
||||
skip_range_deletions = true;
|
||||
@@ -3635,6 +3630,7 @@ void VersionStorageInfo::ComputeCompactionScore(
|
||||
void VersionStorageInfo::ComputeFilesMarkedForCompaction(int last_level) {
|
||||
files_marked_for_compaction_.clear();
|
||||
int last_qualify_level = 0;
|
||||
standalone_range_tombstone_files_mark_threshold_ = kMaxSequenceNumber;
|
||||
|
||||
// Do not include files from the last level with data
|
||||
// If table properties collector suggests a file on the last level,
|
||||
@@ -3650,6 +3646,11 @@ void VersionStorageInfo::ComputeFilesMarkedForCompaction(int last_level) {
|
||||
for (auto* f : files_[level]) {
|
||||
if (!f->being_compacted && f->marked_for_compaction) {
|
||||
files_marked_for_compaction_.emplace_back(level, f);
|
||||
if (f->FileIsStandAloneRangeTombstone()) {
|
||||
standalone_range_tombstone_files_mark_threshold_ =
|
||||
std::min(standalone_range_tombstone_files_mark_threshold_,
|
||||
f->fd.smallest_seqno);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5533,10 +5534,8 @@ Status VersionSet::ProcessManifestWrites(
|
||||
s = builder_guards[i]->version_builder()->LoadTableHandlers(
|
||||
cfd->internal_stats(), 1 /* max_threads */,
|
||||
true /* prefetch_index_and_filter_in_cache */,
|
||||
false /* is_initial_load */,
|
||||
mutable_cf_options_ptrs[i]->prefix_extractor,
|
||||
MaxFileSizeForL0MetaPin(*mutable_cf_options_ptrs[i]), read_options,
|
||||
mutable_cf_options_ptrs[i]->block_protection_bytes_per_key);
|
||||
false /* is_initial_load */, *mutable_cf_options_ptrs[i],
|
||||
MaxFileSizeForL0MetaPin(*mutable_cf_options_ptrs[i]), read_options);
|
||||
if (!s.ok()) {
|
||||
if (db_options_->paranoid_checks) {
|
||||
break;
|
||||
@@ -6015,15 +6014,19 @@ Status VersionSet::LogAndApplyHelper(ColumnFamilyData* cfd,
|
||||
}
|
||||
|
||||
Status VersionSet::GetCurrentManifestPath(const std::string& dbname,
|
||||
FileSystem* fs,
|
||||
FileSystem* fs, bool is_retry,
|
||||
std::string* manifest_path,
|
||||
uint64_t* manifest_file_number) {
|
||||
assert(fs != nullptr);
|
||||
assert(manifest_path != nullptr);
|
||||
assert(manifest_file_number != nullptr);
|
||||
|
||||
IOOptions opts;
|
||||
std::string fname;
|
||||
Status s = ReadFileToString(fs, CurrentFileName(dbname), &fname);
|
||||
if (is_retry) {
|
||||
opts.verify_and_reconstruct_read = true;
|
||||
}
|
||||
Status s = ReadFileToString(fs, CurrentFileName(dbname), opts, &fname);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -6053,8 +6056,8 @@ Status VersionSet::Recover(
|
||||
// Read "CURRENT" file, which contains a pointer to the current manifest
|
||||
// file
|
||||
std::string manifest_path;
|
||||
Status s = GetCurrentManifestPath(dbname_, fs_.get(), &manifest_path,
|
||||
&manifest_file_number_);
|
||||
Status s = GetCurrentManifestPath(dbname_, fs_.get(), is_retry,
|
||||
&manifest_path, &manifest_file_number_);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -6299,8 +6302,8 @@ Status VersionSet::ListColumnFamilies(std::vector<std::string>* column_families,
|
||||
// Read "CURRENT" file, which contains a pointer to the current manifest file
|
||||
std::string manifest_path;
|
||||
uint64_t manifest_file_number;
|
||||
Status s =
|
||||
GetCurrentManifestPath(dbname, fs, &manifest_path, &manifest_file_number);
|
||||
Status s = GetCurrentManifestPath(dbname, fs, /*is_retry=*/false,
|
||||
&manifest_path, &manifest_file_number);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -6923,8 +6926,7 @@ uint64_t VersionSet::ApproximateOffsetOf(const ReadOptions& read_options,
|
||||
const MutableCFOptions& cf_opts = v->GetMutableCFOptions();
|
||||
if (table_cache != nullptr) {
|
||||
result = table_cache->ApproximateOffsetOf(
|
||||
read_options, key, *f.file_metadata, caller, icmp,
|
||||
cf_opts.block_protection_bytes_per_key, cf_opts.prefix_extractor);
|
||||
read_options, key, *f.file_metadata, caller, icmp, cf_opts);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
@@ -6965,9 +6967,8 @@ uint64_t VersionSet::ApproximateSize(const ReadOptions& read_options,
|
||||
return 0;
|
||||
}
|
||||
const MutableCFOptions& cf_opts = v->GetMutableCFOptions();
|
||||
return table_cache->ApproximateSize(
|
||||
read_options, start, end, *f.file_metadata, caller, icmp,
|
||||
cf_opts.block_protection_bytes_per_key, cf_opts.prefix_extractor);
|
||||
return table_cache->ApproximateSize(read_options, start, end,
|
||||
*f.file_metadata, caller, icmp, cf_opts);
|
||||
}
|
||||
|
||||
void VersionSet::RemoveLiveFiles(
|
||||
@@ -7093,10 +7094,12 @@ InternalIterator* VersionSet::MakeInputIterator(
|
||||
std::unique_ptr<TruncatedRangeDelIterator>**>>
|
||||
range_tombstones;
|
||||
size_t num = 0;
|
||||
[[maybe_unused]] size_t num_input_files = 0;
|
||||
for (size_t which = 0; which < c->num_input_levels(); which++) {
|
||||
if (c->input_levels(which)->num_files != 0) {
|
||||
const LevelFilesBrief* flevel = c->input_levels(which);
|
||||
num_input_files += flevel->num_files;
|
||||
if (flevel->num_files != 0) {
|
||||
if (c->level(which) == 0) {
|
||||
const LevelFilesBrief* flevel = c->input_levels(which);
|
||||
for (size_t i = 0; i < flevel->num_files; i++) {
|
||||
const FileMetaData& fmd = *flevel->files[i].file_metadata;
|
||||
if (start.has_value() &&
|
||||
@@ -7117,7 +7120,7 @@ InternalIterator* VersionSet::MakeInputIterator(
|
||||
list[num++] = cfd->table_cache()->NewIterator(
|
||||
read_options, file_options_compactions,
|
||||
cfd->internal_comparator(), fmd, range_del_agg,
|
||||
c->mutable_cf_options()->prefix_extractor,
|
||||
*c->mutable_cf_options(),
|
||||
/*table_reader_ptr=*/nullptr,
|
||||
/*file_read_hist=*/nullptr, TableReaderCaller::kCompaction,
|
||||
/*arena=*/nullptr,
|
||||
@@ -7127,7 +7130,6 @@ InternalIterator* VersionSet::MakeInputIterator(
|
||||
/*smallest_compaction_key=*/nullptr,
|
||||
/*largest_compaction_key=*/nullptr,
|
||||
/*allow_unprepared_value=*/false,
|
||||
c->mutable_cf_options()->block_protection_bytes_per_key,
|
||||
/*range_del_read_seqno=*/nullptr,
|
||||
/*range_del_iter=*/&range_tombstone_iter);
|
||||
range_tombstones.emplace_back(std::move(range_tombstone_iter),
|
||||
@@ -7139,18 +7141,19 @@ InternalIterator* VersionSet::MakeInputIterator(
|
||||
nullptr;
|
||||
list[num++] = new LevelIterator(
|
||||
cfd->table_cache(), read_options, file_options_compactions,
|
||||
cfd->internal_comparator(), c->input_levels(which),
|
||||
c->mutable_cf_options()->prefix_extractor,
|
||||
cfd->internal_comparator(), flevel, *c->mutable_cf_options(),
|
||||
/*should_sample=*/false,
|
||||
/*no per level latency histogram=*/nullptr,
|
||||
TableReaderCaller::kCompaction, /*skip_filters=*/false,
|
||||
/*level=*/static_cast<int>(c->level(which)),
|
||||
c->mutable_cf_options()->block_protection_bytes_per_key,
|
||||
range_del_agg, c->boundaries(which), false, &tombstone_iter_ptr);
|
||||
/*level=*/static_cast<int>(c->level(which)), range_del_agg,
|
||||
c->boundaries(which), false, &tombstone_iter_ptr);
|
||||
range_tombstones.emplace_back(nullptr, tombstone_iter_ptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
TEST_SYNC_POINT_CALLBACK(
|
||||
"VersionSet::MakeInputIterator:NewCompactionMergingIterator",
|
||||
&num_input_files);
|
||||
assert(num <= space);
|
||||
InternalIterator* result = NewCompactionMergingIterator(
|
||||
&c->column_family_data()->internal_comparator(), list,
|
||||
@@ -7391,7 +7394,6 @@ Status VersionSet::VerifyFileMetadata(const ReadOptions& read_options,
|
||||
|
||||
const MutableCFOptions* const cf_opts = cfd->GetLatestMutableCFOptions();
|
||||
assert(cf_opts);
|
||||
std::shared_ptr<const SliceTransform> pe = cf_opts->prefix_extractor;
|
||||
size_t max_sz_for_l0_meta_pin = MaxFileSizeForL0MetaPin(*cf_opts);
|
||||
|
||||
const FileOptions& file_opts = file_options();
|
||||
@@ -7407,8 +7409,7 @@ Status VersionSet::VerifyFileMetadata(const ReadOptions& read_options,
|
||||
TableCache::TypedHandle* handle = nullptr;
|
||||
FileMetaData meta_copy = meta;
|
||||
status = table_cache->FindTable(
|
||||
read_options, file_opts, *icmp, meta_copy, &handle,
|
||||
cf_opts->block_protection_bytes_per_key, pe,
|
||||
read_options, file_opts, *icmp, meta_copy, &handle, *cf_opts,
|
||||
/*no_io=*/false, internal_stats->GetFileReadHist(level), false, level,
|
||||
/*prefetch_index_and_filter_in_cache*/ false, max_sz_for_l0_meta_pin,
|
||||
meta_copy.temperature);
|
||||
@@ -7500,8 +7501,8 @@ Status ReactiveVersionSet::MaybeSwitchManifest(
|
||||
assert(manifest_reader != nullptr);
|
||||
Status s;
|
||||
std::string manifest_path;
|
||||
s = GetCurrentManifestPath(dbname_, fs_.get(), &manifest_path,
|
||||
&manifest_file_number_);
|
||||
s = GetCurrentManifestPath(dbname_, fs_.get(), /*is_retry=*/false,
|
||||
&manifest_path, &manifest_file_number_);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
+13
-2
@@ -616,6 +616,10 @@ class VersionStorageInfo {
|
||||
return bottommost_files_mark_threshold_;
|
||||
}
|
||||
|
||||
SequenceNumber standalone_range_tombstone_files_mark_threshold() const {
|
||||
return standalone_range_tombstone_files_mark_threshold_;
|
||||
}
|
||||
|
||||
// Returns whether any key in [`smallest_key`, `largest_key`] could appear in
|
||||
// an older L0 file than `last_l0_idx` or in a greater level than `last_level`
|
||||
//
|
||||
@@ -628,6 +632,8 @@ class VersionStorageInfo {
|
||||
|
||||
Env::WriteLifeTimeHint CalculateSSTWriteHint(int level) const;
|
||||
|
||||
const Comparator* user_comparator() const { return user_comparator_; }
|
||||
|
||||
private:
|
||||
void ComputeCompensatedSizes();
|
||||
void UpdateNumNonEmptyLevels();
|
||||
@@ -732,6 +738,12 @@ class VersionStorageInfo {
|
||||
// seqnums of unmarked bottommost files.
|
||||
SequenceNumber bottommost_files_mark_threshold_ = kMaxSequenceNumber;
|
||||
|
||||
// The minimum sequence number among all the standalone range tombstone files
|
||||
// that are marked for compaction. A standalone range tombstone file is one
|
||||
// with just one range tombstone.
|
||||
SequenceNumber standalone_range_tombstone_files_mark_threshold_ =
|
||||
kMaxSequenceNumber;
|
||||
|
||||
// Monotonically increases as we release old snapshots. Zero indicates no
|
||||
// snapshots have been released yet. When no snapshots remain we set it to the
|
||||
// current seqnum, which needs to be protected as a snapshot can still be
|
||||
@@ -1266,7 +1278,7 @@ class VersionSet {
|
||||
{});
|
||||
|
||||
static Status GetCurrentManifestPath(const std::string& dbname,
|
||||
FileSystem* fs,
|
||||
FileSystem* fs, bool is_retry,
|
||||
std::string* manifest_filename,
|
||||
uint64_t* manifest_file_number);
|
||||
void WakeUpWaitingManifestWriters();
|
||||
@@ -1514,7 +1526,6 @@ class VersionSet {
|
||||
void GetLiveFilesMetaData(std::vector<LiveFileMetaData>* metadata);
|
||||
|
||||
void AddObsoleteBlobFile(uint64_t blob_file_number, std::string path) {
|
||||
// TODO: Erase file from BlobFileCache?
|
||||
obsolete_blob_files_.emplace_back(blob_file_number, std::move(path));
|
||||
}
|
||||
|
||||
|
||||
Loaded 100 of 204 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user