mirror of
https://github.com/facebook/rocksdb.git
synced 2026-11-06 19:12:36 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
72820d70cf | ||
|
|
145a50ba00 | ||
|
|
0607f17755 | ||
|
|
2e2c87a53d | ||
|
|
f32521662a | ||
|
|
fad0f3d34e | ||
|
|
5e063b9588 | ||
|
|
3885d76524 | ||
|
|
89a3958bcc | ||
|
|
6fd663a22f | ||
|
|
393d2ddf38 | ||
|
|
69ddf2e0f6 | ||
|
|
05f2425452 | ||
|
|
1567108fc1 | ||
|
|
629605d645 | ||
|
|
df543460d5 | ||
|
|
6628ff12d6 | ||
|
|
86634885eb | ||
|
|
64b0439bc1 | ||
|
|
2f712235ab | ||
|
|
aeda36e925 | ||
|
|
98d0f6ec08 | ||
|
|
8a7b9888d4 | ||
|
|
977aae53d2 | ||
|
|
05c3b8ecac | ||
|
|
ff0d618c7f | ||
|
|
846db9d7b1 | ||
|
|
662a1c99f6 | ||
|
|
749b179c04 | ||
|
|
b1b6f87fbe | ||
|
|
bc0db33483 | ||
|
|
c3c84b3397 | ||
|
|
854eb76a8c | ||
|
|
1a7c741977 | ||
|
|
f74526341d | ||
|
|
baf37a0e81 | ||
|
|
1f410ff95f | ||
|
|
df082c8d1d | ||
|
|
c53d604f41 | ||
|
|
25b08eb438 | ||
|
|
f4e304f987 | ||
|
|
15053f3ab4 | ||
|
|
bfdc91017c | ||
|
|
5187ac2af3 | ||
|
|
f7aa70a72f | ||
|
|
5732cf50e1 | ||
|
|
44524cf5da | ||
|
|
b14c0b0602 | ||
|
|
94c247bff8 |
No files matched your search
@@ -1592,3 +1592,6 @@ option(WITH_BENCHMARK "build benchmark tests" OFF)
|
||||
if(WITH_BENCHMARK)
|
||||
add_subdirectory(${PROJECT_SOURCE_DIR}/microbench/)
|
||||
endif()
|
||||
|
||||
target_include_directories(${PROJECT_NAME} PUBLIC
|
||||
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>)
|
||||
+30
-5
@@ -1,15 +1,40 @@
|
||||
# Rocksdb Change Log
|
||||
> NOTE: Entries for next release do not go here. Follow instructions in `unreleased_history/README.txt`
|
||||
|
||||
## 8.4.4 (09/01/2023)
|
||||
## 8.5.4 (09/26/2023)
|
||||
### Bug Fixes
|
||||
* Fixed a bug where compaction read under non direct IO still falls back to RocksDB internal prefetching after file system's prefetching returns non-OK status other than `Status::NotSupported()`
|
||||
|
||||
### Behavior Changes
|
||||
* For non direct IO, eliminate the file system prefetching attempt for compaction read when `Options::compaction_readahead_size` is 0
|
||||
|
||||
## 8.5.3 (09/01/2023)
|
||||
### Bug Fixes
|
||||
* Fix a bug where if there is an error reading from offset 0 of a file from L1+ and that the file is not the first file in the sorted run, data can be lost in compaction and read/scan can return incorrect results.
|
||||
* Fix a bug where iterator may return incorrect result for DeleteRange() users if there was an error reading from a file.
|
||||
* Fixed a race condition in `GenericRateLimiter` that could cause it to stop granting requests
|
||||
|
||||
## 8.4.3 (07/27/2023)
|
||||
## 8.5.2 (08/31/2023)
|
||||
### Bug fixes
|
||||
* Fix a bug where iterator may return incorrect result for DeleteRange() users if there was an error reading from a file.
|
||||
|
||||
## 8.5.1 (08/31/2023)
|
||||
### Bug fixes
|
||||
* Fix a bug where if there is an error reading from offset 0 of a file from L1+ and that the file is not the first file in the sorted run, data can be lost in compaction and read/scan can return incorrect results.
|
||||
|
||||
## 8.5.0 (07/21/2023)
|
||||
### Public API Changes
|
||||
* Removed recently added APIs `GeneralCache` and `MakeSharedGeneralCache()` as our plan changed to stop exposing a general-purpose cache interface. The old forms of these APIs, `Cache` and `NewLRUCache()`, are still available, although general-purpose caching support will be dropped eventually.
|
||||
|
||||
### Behavior Changes
|
||||
* Option `periodic_compaction_seconds` no longer supports FIFO compaction: setting it has no effect on FIFO compactions. FIFO compaction users should only set option `ttl` instead.
|
||||
* Move prefetching responsibility to page cache for compaction read for non directIO use case
|
||||
|
||||
### Performance Improvements
|
||||
* In case of direct_io, if buffer passed by callee is already aligned, RandomAccessFileRead::Read will avoid realloacting a new buffer, reducing memcpy and use already passed aligned buffer.
|
||||
* Small efficiency improvement to HyperClockCache by reducing chance of compiler-generated heap allocations
|
||||
|
||||
### Bug Fixes
|
||||
* Fix use_after_free bug in async_io MultiReads when underlying FS enabled kFSBuffer. kFSBuffer is when underlying FS pass their own buffer instead of using RocksDB scratch in FSReadRequest.
|
||||
* Fix use_after_free bug in async_io MultiReads when underlying FS enabled kFSBuffer. kFSBuffer is when underlying FS pass their own buffer instead of using RocksDB scratch in FSReadRequest. Right now it's an experimental feature.
|
||||
* Fix a bug in FileTTLBooster that can cause users with a large number of levels (more than 65) to see errors like "runtime error: shift exponent .. is too large.." (#11673).
|
||||
|
||||
## 8.4.0 (06/26/2023)
|
||||
### New Features
|
||||
|
||||
Vendored
+8
@@ -77,6 +77,10 @@ DEFINE_bool(lean, false,
|
||||
"If true, no additional computation is performed besides cache "
|
||||
"operations.");
|
||||
|
||||
DEFINE_bool(early_exit, false,
|
||||
"Exit before deallocating most memory. Good for malloc stats, e.g."
|
||||
"MALLOC_CONF=\"stats_print:true\"");
|
||||
|
||||
DEFINE_string(secondary_cache_uri, "",
|
||||
"Full URI for creating a custom secondary cache object");
|
||||
static class std::shared_ptr<ROCKSDB_NAMESPACE::SecondaryCache> secondary_cache;
|
||||
@@ -593,6 +597,10 @@ class CacheBench {
|
||||
}
|
||||
thread->latency_ns_hist.Add(timer.ElapsedNanos());
|
||||
}
|
||||
if (FLAGS_early_exit) {
|
||||
MutexLock l(thread->shared->GetMutex());
|
||||
exit(0);
|
||||
}
|
||||
if (handle) {
|
||||
cache_->Release(handle);
|
||||
handle = nullptr;
|
||||
|
||||
Vendored
+426
-344
@@ -9,6 +9,7 @@
|
||||
|
||||
#include "cache/clock_cache.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <functional>
|
||||
#include <numeric>
|
||||
|
||||
@@ -118,74 +119,6 @@ inline bool ClockUpdate(ClockHandle& h) {
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void ClockHandleBasicData::FreeData(MemoryAllocator* allocator) const {
|
||||
if (helper->del_cb) {
|
||||
helper->del_cb(value, allocator);
|
||||
}
|
||||
}
|
||||
|
||||
HyperClockTable::HyperClockTable(
|
||||
size_t capacity, bool /*strict_capacity_limit*/,
|
||||
CacheMetadataChargePolicy metadata_charge_policy,
|
||||
MemoryAllocator* allocator,
|
||||
const Cache::EvictionCallback* eviction_callback, const uint32_t* hash_seed,
|
||||
const Opts& opts)
|
||||
: length_bits_(CalcHashBits(capacity, opts.estimated_value_size,
|
||||
metadata_charge_policy)),
|
||||
length_bits_mask_((size_t{1} << length_bits_) - 1),
|
||||
occupancy_limit_(static_cast<size_t>((uint64_t{1} << length_bits_) *
|
||||
kStrictLoadFactor)),
|
||||
array_(new HandleImpl[size_t{1} << length_bits_]),
|
||||
allocator_(allocator),
|
||||
eviction_callback_(*eviction_callback),
|
||||
hash_seed_(*hash_seed) {
|
||||
if (metadata_charge_policy ==
|
||||
CacheMetadataChargePolicy::kFullChargeCacheMetadata) {
|
||||
usage_ += size_t{GetTableSize()} * sizeof(HandleImpl);
|
||||
}
|
||||
|
||||
static_assert(sizeof(HandleImpl) == 64U,
|
||||
"Expecting size / alignment with common cache line size");
|
||||
}
|
||||
|
||||
HyperClockTable::~HyperClockTable() {
|
||||
// Assumes there are no references or active operations on any slot/element
|
||||
// in the table.
|
||||
for (size_t i = 0; i < GetTableSize(); i++) {
|
||||
HandleImpl& h = array_[i];
|
||||
switch (h.meta >> ClockHandle::kStateShift) {
|
||||
case ClockHandle::kStateEmpty:
|
||||
// noop
|
||||
break;
|
||||
case ClockHandle::kStateInvisible: // rare but possible
|
||||
case ClockHandle::kStateVisible:
|
||||
assert(GetRefcount(h.meta) == 0);
|
||||
h.FreeData(allocator_);
|
||||
#ifndef NDEBUG
|
||||
Rollback(h.hashed_key, &h);
|
||||
ReclaimEntryUsage(h.GetTotalCharge());
|
||||
#endif
|
||||
break;
|
||||
// otherwise
|
||||
default:
|
||||
assert(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
for (size_t i = 0; i < GetTableSize(); i++) {
|
||||
assert(array_[i].displacements.load() == 0);
|
||||
}
|
||||
#endif
|
||||
|
||||
assert(usage_.load() == 0 ||
|
||||
usage_.load() == size_t{GetTableSize()} * sizeof(HandleImpl));
|
||||
assert(occupancy_ == 0);
|
||||
}
|
||||
|
||||
// If an entry doesn't receive clock updates but is repeatedly referenced &
|
||||
// released, the acquire and release counters could overflow without some
|
||||
// intervention. This is that intervention, which should be inexpensive
|
||||
@@ -259,8 +192,209 @@ inline void CorrectNearOverflow(uint64_t old_meta,
|
||||
}
|
||||
}
|
||||
|
||||
inline Status HyperClockTable::ChargeUsageMaybeEvictStrict(
|
||||
size_t total_charge, size_t capacity, bool need_evict_for_occupancy) {
|
||||
inline bool BeginSlotInsert(const ClockHandleBasicData& proto, ClockHandle& h,
|
||||
uint64_t initial_countdown, bool* already_matches) {
|
||||
assert(*already_matches == false);
|
||||
// Optimistically transition the slot from "empty" to
|
||||
// "under construction" (no effect on other states)
|
||||
uint64_t old_meta = h.meta.fetch_or(
|
||||
uint64_t{ClockHandle::kStateOccupiedBit} << ClockHandle::kStateShift,
|
||||
std::memory_order_acq_rel);
|
||||
uint64_t old_state = old_meta >> ClockHandle::kStateShift;
|
||||
|
||||
if (old_state == ClockHandle::kStateEmpty) {
|
||||
// We've started inserting into an available slot, and taken
|
||||
// ownership.
|
||||
return true;
|
||||
} else if (old_state != ClockHandle::kStateVisible) {
|
||||
// Slot not usable / touchable now
|
||||
return false;
|
||||
}
|
||||
// Existing, visible entry, which might be a match.
|
||||
// But first, we need to acquire a ref to read it. In fact, number of
|
||||
// refs for initial countdown, so that we boost the clock state if
|
||||
// this is a match.
|
||||
old_meta =
|
||||
h.meta.fetch_add(ClockHandle::kAcquireIncrement * initial_countdown,
|
||||
std::memory_order_acq_rel);
|
||||
// Like Lookup
|
||||
if ((old_meta >> ClockHandle::kStateShift) == ClockHandle::kStateVisible) {
|
||||
// Acquired a read reference
|
||||
if (h.hashed_key == proto.hashed_key) {
|
||||
// Match. Release in a way that boosts the clock state
|
||||
old_meta =
|
||||
h.meta.fetch_add(ClockHandle::kReleaseIncrement * initial_countdown,
|
||||
std::memory_order_acq_rel);
|
||||
// Correct for possible (but rare) overflow
|
||||
CorrectNearOverflow(old_meta, h.meta);
|
||||
// Insert detached instead (only if return handle needed)
|
||||
*already_matches = true;
|
||||
return false;
|
||||
} else {
|
||||
// Mismatch. Pretend we never took the reference
|
||||
old_meta =
|
||||
h.meta.fetch_sub(ClockHandle::kAcquireIncrement * initial_countdown,
|
||||
std::memory_order_acq_rel);
|
||||
}
|
||||
} else if (UNLIKELY((old_meta >> ClockHandle::kStateShift) ==
|
||||
ClockHandle::kStateInvisible)) {
|
||||
// Pretend we never took the reference
|
||||
// WART/FIXME?: there's a tiny chance we release last ref to invisible
|
||||
// entry here. If that happens, we let eviction take care of it.
|
||||
old_meta =
|
||||
h.meta.fetch_sub(ClockHandle::kAcquireIncrement * initial_countdown,
|
||||
std::memory_order_acq_rel);
|
||||
} else {
|
||||
// For other states, incrementing the acquire counter has no effect
|
||||
// so we don't need to undo it.
|
||||
// Slot not usable / touchable now.
|
||||
}
|
||||
(void)old_meta;
|
||||
return false;
|
||||
}
|
||||
|
||||
inline void FinishSlotInsert(const ClockHandleBasicData& proto, ClockHandle& h,
|
||||
uint64_t initial_countdown, bool keep_ref) {
|
||||
// Save data fields
|
||||
ClockHandleBasicData* h_alias = &h;
|
||||
*h_alias = proto;
|
||||
|
||||
// Transition from "under construction" state to "visible" state
|
||||
uint64_t new_meta = uint64_t{ClockHandle::kStateVisible}
|
||||
<< ClockHandle::kStateShift;
|
||||
|
||||
// Maybe with an outstanding reference
|
||||
new_meta |= initial_countdown << ClockHandle::kAcquireCounterShift;
|
||||
new_meta |= (initial_countdown - keep_ref)
|
||||
<< ClockHandle::kReleaseCounterShift;
|
||||
|
||||
#ifndef NDEBUG
|
||||
// Save the state transition, with assertion
|
||||
uint64_t old_meta = h.meta.exchange(new_meta, std::memory_order_release);
|
||||
assert(old_meta >> ClockHandle::kStateShift ==
|
||||
ClockHandle::kStateConstruction);
|
||||
#else
|
||||
// Save the state transition
|
||||
h.meta.store(new_meta, std::memory_order_release);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool TryInsert(const ClockHandleBasicData& proto, ClockHandle& h,
|
||||
uint64_t initial_countdown, bool keep_ref,
|
||||
bool* already_matches) {
|
||||
bool b = BeginSlotInsert(proto, h, initial_countdown, already_matches);
|
||||
if (b) {
|
||||
FinishSlotInsert(proto, h, initial_countdown, keep_ref);
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
template <class HandleImpl, class Func>
|
||||
void ConstApplyToEntriesRange(Func /*const HandleImpl& -> void*/ func,
|
||||
const HandleImpl* begin, const HandleImpl* end,
|
||||
bool apply_if_will_be_deleted) {
|
||||
uint64_t check_state_mask = ClockHandle::kStateShareableBit;
|
||||
if (!apply_if_will_be_deleted) {
|
||||
check_state_mask |= ClockHandle::kStateVisibleBit;
|
||||
}
|
||||
|
||||
for (const HandleImpl* h = begin; h < end; ++h) {
|
||||
// Note: to avoid using compare_exchange, we have to be extra careful.
|
||||
uint64_t old_meta = h->meta.load(std::memory_order_relaxed);
|
||||
// Check if it's an entry visible to lookups
|
||||
if ((old_meta >> ClockHandle::kStateShift) & check_state_mask) {
|
||||
// Increment acquire counter. Note: it's possible that the entry has
|
||||
// completely changed since we loaded old_meta, but incrementing acquire
|
||||
// count is always safe. (Similar to optimistic Lookup here.)
|
||||
old_meta = h->meta.fetch_add(ClockHandle::kAcquireIncrement,
|
||||
std::memory_order_acquire);
|
||||
// Check whether we actually acquired a reference.
|
||||
if ((old_meta >> ClockHandle::kStateShift) &
|
||||
ClockHandle::kStateShareableBit) {
|
||||
// Apply func if appropriate
|
||||
if ((old_meta >> ClockHandle::kStateShift) & check_state_mask) {
|
||||
func(*h);
|
||||
}
|
||||
// Pretend we never took the reference
|
||||
h->meta.fetch_sub(ClockHandle::kAcquireIncrement,
|
||||
std::memory_order_release);
|
||||
// No net change, so don't need to check for overflow
|
||||
} else {
|
||||
// For other states, incrementing the acquire counter has no effect
|
||||
// so we don't need to undo it. Furthermore, we cannot safely undo
|
||||
// it because we did not acquire a read reference to lock the
|
||||
// entry in a Shareable state.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void ClockHandleBasicData::FreeData(MemoryAllocator* allocator) const {
|
||||
if (helper->del_cb) {
|
||||
helper->del_cb(value, allocator);
|
||||
}
|
||||
}
|
||||
|
||||
template <class HandleImpl>
|
||||
HandleImpl* BaseClockTable::StandaloneInsert(
|
||||
const ClockHandleBasicData& proto) {
|
||||
// Heap allocated separate from table
|
||||
HandleImpl* h = new HandleImpl();
|
||||
ClockHandleBasicData* h_alias = h;
|
||||
*h_alias = proto;
|
||||
h->SetStandalone();
|
||||
// Single reference (standalone entries only created if returning a refed
|
||||
// Handle back to user)
|
||||
uint64_t meta = uint64_t{ClockHandle::kStateInvisible}
|
||||
<< ClockHandle::kStateShift;
|
||||
meta |= uint64_t{1} << ClockHandle::kAcquireCounterShift;
|
||||
h->meta.store(meta, std::memory_order_release);
|
||||
// Keep track of how much of usage is standalone
|
||||
standalone_usage_.fetch_add(proto.GetTotalCharge(),
|
||||
std::memory_order_relaxed);
|
||||
return h;
|
||||
}
|
||||
|
||||
template <class Table>
|
||||
typename Table::HandleImpl* BaseClockTable::CreateStandalone(
|
||||
ClockHandleBasicData& proto, size_t capacity, bool strict_capacity_limit,
|
||||
bool allow_uncharged) {
|
||||
Table& derived = static_cast<Table&>(*this);
|
||||
typename Table::InsertState state;
|
||||
derived.StartInsert(state);
|
||||
|
||||
const size_t total_charge = proto.GetTotalCharge();
|
||||
if (strict_capacity_limit) {
|
||||
Status s = ChargeUsageMaybeEvictStrict<Table>(
|
||||
total_charge, capacity,
|
||||
/*need_evict_for_occupancy=*/false, state);
|
||||
if (!s.ok()) {
|
||||
if (allow_uncharged) {
|
||||
proto.total_charge = 0;
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Case strict_capacity_limit == false
|
||||
bool success = ChargeUsageMaybeEvictNonStrict<Table>(
|
||||
total_charge, capacity,
|
||||
/*need_evict_for_occupancy=*/false, state);
|
||||
if (!success) {
|
||||
// Force the issue
|
||||
usage_.fetch_add(total_charge, std::memory_order_relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
return StandaloneInsert<typename Table::HandleImpl>(proto);
|
||||
}
|
||||
|
||||
template <class Table>
|
||||
Status BaseClockTable::ChargeUsageMaybeEvictStrict(
|
||||
size_t total_charge, size_t capacity, bool need_evict_for_occupancy,
|
||||
typename Table::InsertState& state) {
|
||||
if (total_charge > capacity) {
|
||||
return Status::MemoryLimit(
|
||||
"Cache entry too large for a single cache shard: " +
|
||||
@@ -287,7 +421,8 @@ inline Status HyperClockTable::ChargeUsageMaybeEvictStrict(
|
||||
if (request_evict_charge > 0) {
|
||||
size_t evicted_charge = 0;
|
||||
size_t evicted_count = 0;
|
||||
Evict(request_evict_charge, &evicted_charge, &evicted_count);
|
||||
static_cast<Table*>(this)->Evict(request_evict_charge, &evicted_charge,
|
||||
&evicted_count, state);
|
||||
occupancy_.fetch_sub(evicted_count, std::memory_order_release);
|
||||
if (LIKELY(evicted_charge > need_evict_charge)) {
|
||||
assert(evicted_count > 0);
|
||||
@@ -316,8 +451,10 @@ inline Status HyperClockTable::ChargeUsageMaybeEvictStrict(
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
inline bool HyperClockTable::ChargeUsageMaybeEvictNonStrict(
|
||||
size_t total_charge, size_t capacity, bool need_evict_for_occupancy) {
|
||||
template <class Table>
|
||||
inline bool BaseClockTable::ChargeUsageMaybeEvictNonStrict(
|
||||
size_t total_charge, size_t capacity, bool need_evict_for_occupancy,
|
||||
typename Table::InsertState& state) {
|
||||
// For simplicity, we consider that either the cache can accept the insert
|
||||
// with no evictions, or we must evict enough to make (at least) enough
|
||||
// space. It could lead to unnecessary failures or excessive evictions in
|
||||
@@ -354,7 +491,8 @@ inline bool HyperClockTable::ChargeUsageMaybeEvictNonStrict(
|
||||
size_t evicted_charge = 0;
|
||||
size_t evicted_count = 0;
|
||||
if (need_evict_charge > 0) {
|
||||
Evict(need_evict_charge, &evicted_charge, &evicted_count);
|
||||
static_cast<Table*>(this)->Evict(need_evict_charge, &evicted_charge,
|
||||
&evicted_count, state);
|
||||
// Deal with potential occupancy deficit
|
||||
if (UNLIKELY(need_evict_for_occupancy) && evicted_count == 0) {
|
||||
assert(evicted_charge == 0);
|
||||
@@ -373,54 +511,43 @@ inline bool HyperClockTable::ChargeUsageMaybeEvictNonStrict(
|
||||
return true;
|
||||
}
|
||||
|
||||
inline HyperClockTable::HandleImpl* HyperClockTable::StandaloneInsert(
|
||||
const ClockHandleBasicData& proto) {
|
||||
// Heap allocated separate from table
|
||||
HandleImpl* h = new HandleImpl();
|
||||
ClockHandleBasicData* h_alias = h;
|
||||
*h_alias = proto;
|
||||
h->SetStandalone();
|
||||
// Single reference (standalone entries only created if returning a refed
|
||||
// Handle back to user)
|
||||
uint64_t meta = uint64_t{ClockHandle::kStateInvisible}
|
||||
<< ClockHandle::kStateShift;
|
||||
meta |= uint64_t{1} << ClockHandle::kAcquireCounterShift;
|
||||
h->meta.store(meta, std::memory_order_release);
|
||||
// Keep track of how much of usage is standalone
|
||||
standalone_usage_.fetch_add(proto.GetTotalCharge(),
|
||||
std::memory_order_relaxed);
|
||||
return h;
|
||||
}
|
||||
template <class Table>
|
||||
Status BaseClockTable::Insert(const ClockHandleBasicData& proto,
|
||||
typename Table::HandleImpl** handle,
|
||||
Cache::Priority priority, size_t capacity,
|
||||
bool strict_capacity_limit) {
|
||||
using HandleImpl = typename Table::HandleImpl;
|
||||
Table& derived = static_cast<Table&>(*this);
|
||||
|
||||
typename Table::InsertState state;
|
||||
derived.StartInsert(state);
|
||||
|
||||
Status HyperClockTable::Insert(const ClockHandleBasicData& proto,
|
||||
HandleImpl** handle, Cache::Priority priority,
|
||||
size_t capacity, bool strict_capacity_limit) {
|
||||
// Do we have the available occupancy? Optimistically assume we do
|
||||
// and deal with it if we don't.
|
||||
size_t old_occupancy = occupancy_.fetch_add(1, std::memory_order_acquire);
|
||||
auto revert_occupancy_fn = [&]() {
|
||||
occupancy_.fetch_sub(1, std::memory_order_relaxed);
|
||||
};
|
||||
// Whether we over-committed and need an eviction to make up for it
|
||||
bool need_evict_for_occupancy = old_occupancy >= occupancy_limit_;
|
||||
bool need_evict_for_occupancy =
|
||||
!derived.GrowIfNeeded(old_occupancy + 1, state);
|
||||
|
||||
// Usage/capacity handling is somewhat different depending on
|
||||
// strict_capacity_limit, but mostly pessimistic.
|
||||
bool use_standalone_insert = false;
|
||||
const size_t total_charge = proto.GetTotalCharge();
|
||||
if (strict_capacity_limit) {
|
||||
Status s = ChargeUsageMaybeEvictStrict(total_charge, capacity,
|
||||
need_evict_for_occupancy);
|
||||
Status s = ChargeUsageMaybeEvictStrict<Table>(
|
||||
total_charge, capacity, need_evict_for_occupancy, state);
|
||||
if (!s.ok()) {
|
||||
revert_occupancy_fn();
|
||||
// Revert occupancy
|
||||
occupancy_.fetch_sub(1, std::memory_order_relaxed);
|
||||
return s;
|
||||
}
|
||||
} else {
|
||||
// Case strict_capacity_limit == false
|
||||
bool success = ChargeUsageMaybeEvictNonStrict(total_charge, capacity,
|
||||
need_evict_for_occupancy);
|
||||
bool success = ChargeUsageMaybeEvictNonStrict<Table>(
|
||||
total_charge, capacity, need_evict_for_occupancy, state);
|
||||
if (!success) {
|
||||
revert_occupancy_fn();
|
||||
// Revert occupancy
|
||||
occupancy_.fetch_sub(1, std::memory_order_relaxed);
|
||||
if (handle == nullptr) {
|
||||
// Don't insert the entry but still return ok, as if the entry
|
||||
// inserted into cache and evicted immediately.
|
||||
@@ -433,11 +560,6 @@ Status HyperClockTable::Insert(const ClockHandleBasicData& proto,
|
||||
}
|
||||
}
|
||||
}
|
||||
auto revert_usage_fn = [&]() {
|
||||
usage_.fetch_sub(total_charge, std::memory_order_relaxed);
|
||||
// No underflow
|
||||
assert(usage_.load(std::memory_order_relaxed) < SIZE_MAX / 2);
|
||||
};
|
||||
|
||||
if (!use_standalone_insert) {
|
||||
// Attempt a table insert, but abort if we find an existing entry for the
|
||||
@@ -451,129 +573,37 @@ Status HyperClockTable::Insert(const ClockHandleBasicData& proto,
|
||||
uint64_t initial_countdown = GetInitialCountdown(priority);
|
||||
assert(initial_countdown > 0);
|
||||
|
||||
size_t probe = 0;
|
||||
HandleImpl* e = FindSlot(
|
||||
proto.hashed_key,
|
||||
[&](HandleImpl* h) {
|
||||
// Optimistically transition the slot from "empty" to
|
||||
// "under construction" (no effect on other states)
|
||||
uint64_t old_meta =
|
||||
h->meta.fetch_or(uint64_t{ClockHandle::kStateOccupiedBit}
|
||||
<< ClockHandle::kStateShift,
|
||||
std::memory_order_acq_rel);
|
||||
uint64_t old_state = old_meta >> ClockHandle::kStateShift;
|
||||
HandleImpl* e =
|
||||
derived.DoInsert(proto, initial_countdown, handle != nullptr, state);
|
||||
|
||||
if (old_state == ClockHandle::kStateEmpty) {
|
||||
// We've started inserting into an available slot, and taken
|
||||
// ownership Save data fields
|
||||
ClockHandleBasicData* h_alias = h;
|
||||
*h_alias = proto;
|
||||
|
||||
// Transition from "under construction" state to "visible" state
|
||||
uint64_t new_meta = uint64_t{ClockHandle::kStateVisible}
|
||||
<< ClockHandle::kStateShift;
|
||||
|
||||
// Maybe with an outstanding reference
|
||||
new_meta |= initial_countdown << ClockHandle::kAcquireCounterShift;
|
||||
new_meta |= (initial_countdown - (handle != nullptr))
|
||||
<< ClockHandle::kReleaseCounterShift;
|
||||
|
||||
#ifndef NDEBUG
|
||||
// Save the state transition, with assertion
|
||||
old_meta = h->meta.exchange(new_meta, std::memory_order_release);
|
||||
assert(old_meta >> ClockHandle::kStateShift ==
|
||||
ClockHandle::kStateConstruction);
|
||||
#else
|
||||
// Save the state transition
|
||||
h->meta.store(new_meta, std::memory_order_release);
|
||||
#endif
|
||||
return true;
|
||||
} else if (old_state != ClockHandle::kStateVisible) {
|
||||
// Slot not usable / touchable now
|
||||
return false;
|
||||
}
|
||||
// Existing, visible entry, which might be a match.
|
||||
// But first, we need to acquire a ref to read it. In fact, number of
|
||||
// refs for initial countdown, so that we boost the clock state if
|
||||
// this is a match.
|
||||
old_meta = h->meta.fetch_add(
|
||||
ClockHandle::kAcquireIncrement * initial_countdown,
|
||||
std::memory_order_acq_rel);
|
||||
// Like Lookup
|
||||
if ((old_meta >> ClockHandle::kStateShift) ==
|
||||
ClockHandle::kStateVisible) {
|
||||
// Acquired a read reference
|
||||
if (h->hashed_key == proto.hashed_key) {
|
||||
// Match. Release in a way that boosts the clock state
|
||||
old_meta = h->meta.fetch_add(
|
||||
ClockHandle::kReleaseIncrement * initial_countdown,
|
||||
std::memory_order_acq_rel);
|
||||
// Correct for possible (but rare) overflow
|
||||
CorrectNearOverflow(old_meta, h->meta);
|
||||
// Insert standalone instead (only if return handle needed)
|
||||
use_standalone_insert = true;
|
||||
return true;
|
||||
} else {
|
||||
// Mismatch. Pretend we never took the reference
|
||||
old_meta = h->meta.fetch_sub(
|
||||
ClockHandle::kAcquireIncrement * initial_countdown,
|
||||
std::memory_order_acq_rel);
|
||||
}
|
||||
} else if (UNLIKELY((old_meta >> ClockHandle::kStateShift) ==
|
||||
ClockHandle::kStateInvisible)) {
|
||||
// Pretend we never took the reference
|
||||
// WART: there's a tiny chance we release last ref to invisible
|
||||
// entry here. If that happens, we let eviction take care of it.
|
||||
old_meta = h->meta.fetch_sub(
|
||||
ClockHandle::kAcquireIncrement * initial_countdown,
|
||||
std::memory_order_acq_rel);
|
||||
} else {
|
||||
// For other states, incrementing the acquire counter has no effect
|
||||
// so we don't need to undo it.
|
||||
// Slot not usable / touchable now.
|
||||
}
|
||||
(void)old_meta;
|
||||
return false;
|
||||
},
|
||||
[&](HandleImpl* /*h*/) { return false; },
|
||||
[&](HandleImpl* h) {
|
||||
h->displacements.fetch_add(1, std::memory_order_relaxed);
|
||||
},
|
||||
probe);
|
||||
if (e == nullptr) {
|
||||
// Occupancy check and never abort FindSlot above should generally
|
||||
// prevent this, except it's theoretically possible for other threads
|
||||
// to evict and replace entries in the right order to hit every slot
|
||||
// when it is populated. Assuming random hashing, the chance of that
|
||||
// should be no higher than pow(kStrictLoadFactor, n) for n slots.
|
||||
// That should be infeasible for roughly n >= 256, so if this assertion
|
||||
// fails, that suggests something is going wrong.
|
||||
assert(GetTableSize() < 256);
|
||||
use_standalone_insert = true;
|
||||
}
|
||||
if (!use_standalone_insert) {
|
||||
if (e) {
|
||||
// Successfully inserted
|
||||
if (handle) {
|
||||
*handle = e;
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
// Roll back table insertion
|
||||
Rollback(proto.hashed_key, e);
|
||||
revert_occupancy_fn();
|
||||
// Not inserted
|
||||
// Revert occupancy
|
||||
occupancy_.fetch_sub(1, std::memory_order_relaxed);
|
||||
// Maybe fall back on standalone insert
|
||||
if (handle == nullptr) {
|
||||
revert_usage_fn();
|
||||
// Revert usage
|
||||
usage_.fetch_sub(total_charge, std::memory_order_relaxed);
|
||||
// No underflow
|
||||
assert(usage_.load(std::memory_order_relaxed) < SIZE_MAX / 2);
|
||||
// As if unrefed entry immdiately evicted
|
||||
proto.FreeData(allocator_);
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
use_standalone_insert = true;
|
||||
}
|
||||
|
||||
// Run standalone insert
|
||||
assert(use_standalone_insert);
|
||||
|
||||
*handle = StandaloneInsert(proto);
|
||||
*handle = StandaloneInsert<HandleImpl>(proto);
|
||||
|
||||
// The OkOverwritten status is used to count "redundant" insertions into
|
||||
// block cache. This implementation doesn't strictly check for redundant
|
||||
@@ -583,37 +613,156 @@ Status HyperClockTable::Insert(const ClockHandleBasicData& proto,
|
||||
return Status::OkOverwritten();
|
||||
}
|
||||
|
||||
HyperClockTable::HandleImpl* HyperClockTable::CreateStandalone(
|
||||
ClockHandleBasicData& proto, size_t capacity, bool strict_capacity_limit,
|
||||
bool allow_uncharged) {
|
||||
const size_t total_charge = proto.GetTotalCharge();
|
||||
if (strict_capacity_limit) {
|
||||
Status s = ChargeUsageMaybeEvictStrict(total_charge, capacity,
|
||||
/*need_evict_for_occupancy=*/false);
|
||||
if (!s.ok()) {
|
||||
if (allow_uncharged) {
|
||||
proto.total_charge = 0;
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Case strict_capacity_limit == false
|
||||
bool success =
|
||||
ChargeUsageMaybeEvictNonStrict(total_charge, capacity,
|
||||
/*need_evict_for_occupancy=*/false);
|
||||
if (!success) {
|
||||
// Force the issue
|
||||
usage_.fetch_add(total_charge, std::memory_order_relaxed);
|
||||
void BaseClockTable::Ref(ClockHandle& h) {
|
||||
// Increment acquire counter
|
||||
uint64_t old_meta = h.meta.fetch_add(ClockHandle::kAcquireIncrement,
|
||||
std::memory_order_acquire);
|
||||
|
||||
assert((old_meta >> ClockHandle::kStateShift) &
|
||||
ClockHandle::kStateShareableBit);
|
||||
// Must have already had a reference
|
||||
assert(GetRefcount(old_meta) > 0);
|
||||
(void)old_meta;
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
void BaseClockTable::TEST_RefN(ClockHandle& h, size_t n) {
|
||||
// Increment acquire counter
|
||||
uint64_t old_meta = h.meta.fetch_add(n * ClockHandle::kAcquireIncrement,
|
||||
std::memory_order_acquire);
|
||||
|
||||
assert((old_meta >> ClockHandle::kStateShift) &
|
||||
ClockHandle::kStateShareableBit);
|
||||
(void)old_meta;
|
||||
}
|
||||
|
||||
void BaseClockTable::TEST_ReleaseNMinus1(ClockHandle* h, size_t n) {
|
||||
assert(n > 0);
|
||||
|
||||
// Like n-1 Releases, but assumes one more will happen in the caller to take
|
||||
// care of anything like erasing an unreferenced, invisible entry.
|
||||
uint64_t old_meta = h->meta.fetch_add(
|
||||
(n - 1) * ClockHandle::kReleaseIncrement, std::memory_order_acquire);
|
||||
assert((old_meta >> ClockHandle::kStateShift) &
|
||||
ClockHandle::kStateShareableBit);
|
||||
(void)old_meta;
|
||||
}
|
||||
#endif
|
||||
|
||||
HyperClockTable::HyperClockTable(
|
||||
size_t capacity, bool /*strict_capacity_limit*/,
|
||||
CacheMetadataChargePolicy metadata_charge_policy,
|
||||
MemoryAllocator* allocator,
|
||||
const Cache::EvictionCallback* eviction_callback, const uint32_t* hash_seed,
|
||||
const Opts& opts)
|
||||
: BaseClockTable(metadata_charge_policy, allocator, eviction_callback,
|
||||
hash_seed),
|
||||
length_bits_(CalcHashBits(capacity, opts.estimated_value_size,
|
||||
metadata_charge_policy)),
|
||||
length_bits_mask_((size_t{1} << length_bits_) - 1),
|
||||
occupancy_limit_(static_cast<size_t>((uint64_t{1} << length_bits_) *
|
||||
kStrictLoadFactor)),
|
||||
array_(new HandleImpl[size_t{1} << length_bits_]) {
|
||||
if (metadata_charge_policy ==
|
||||
CacheMetadataChargePolicy::kFullChargeCacheMetadata) {
|
||||
usage_ += size_t{GetTableSize()} * sizeof(HandleImpl);
|
||||
}
|
||||
|
||||
static_assert(sizeof(HandleImpl) == 64U,
|
||||
"Expecting size / alignment with common cache line size");
|
||||
}
|
||||
|
||||
HyperClockTable::~HyperClockTable() {
|
||||
// Assumes there are no references or active operations on any slot/element
|
||||
// in the table.
|
||||
for (size_t i = 0; i < GetTableSize(); i++) {
|
||||
HandleImpl& h = array_[i];
|
||||
switch (h.meta >> ClockHandle::kStateShift) {
|
||||
case ClockHandle::kStateEmpty:
|
||||
// noop
|
||||
break;
|
||||
case ClockHandle::kStateInvisible: // rare but possible
|
||||
case ClockHandle::kStateVisible:
|
||||
assert(GetRefcount(h.meta) == 0);
|
||||
h.FreeData(allocator_);
|
||||
#ifndef NDEBUG
|
||||
Rollback(h.hashed_key, &h);
|
||||
ReclaimEntryUsage(h.GetTotalCharge());
|
||||
#endif
|
||||
break;
|
||||
// otherwise
|
||||
default:
|
||||
assert(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return StandaloneInsert(proto);
|
||||
#ifndef NDEBUG
|
||||
for (size_t i = 0; i < GetTableSize(); i++) {
|
||||
assert(array_[i].displacements.load() == 0);
|
||||
}
|
||||
#endif
|
||||
|
||||
assert(usage_.load() == 0 ||
|
||||
usage_.load() == size_t{GetTableSize()} * sizeof(HandleImpl));
|
||||
assert(occupancy_ == 0);
|
||||
}
|
||||
|
||||
void HyperClockTable::StartInsert(InsertState&) {}
|
||||
|
||||
bool HyperClockTable::GrowIfNeeded(size_t new_occupancy, InsertState&) {
|
||||
return new_occupancy <= occupancy_limit_;
|
||||
}
|
||||
|
||||
HyperClockTable::HandleImpl* HyperClockTable::DoInsert(
|
||||
const ClockHandleBasicData& proto, uint64_t initial_countdown,
|
||||
bool keep_ref, InsertState&) {
|
||||
bool already_matches = false;
|
||||
HandleImpl* e = FindSlot(
|
||||
proto.hashed_key,
|
||||
[&](HandleImpl* h) {
|
||||
return TryInsert(proto, *h, initial_countdown, keep_ref,
|
||||
&already_matches);
|
||||
},
|
||||
[&](HandleImpl* h) {
|
||||
if (already_matches) {
|
||||
// Stop searching & roll back displacements
|
||||
Rollback(proto.hashed_key, h);
|
||||
return true;
|
||||
} else {
|
||||
// Keep going
|
||||
return false;
|
||||
}
|
||||
},
|
||||
[&](HandleImpl* h, bool is_last) {
|
||||
if (is_last) {
|
||||
// Search is ending. Roll back displacements
|
||||
Rollback(proto.hashed_key, h);
|
||||
} else {
|
||||
h->displacements.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
});
|
||||
if (already_matches) {
|
||||
// Insertion skipped
|
||||
return nullptr;
|
||||
}
|
||||
if (e != nullptr) {
|
||||
// Successfully inserted
|
||||
return e;
|
||||
}
|
||||
// Else, no available slot found. Occupancy check should generally prevent
|
||||
// this, except it's theoretically possible for other threads to evict and
|
||||
// replace entries in the right order to hit every slot when it is populated.
|
||||
// Assuming random hashing, the chance of that should be no higher than
|
||||
// pow(kStrictLoadFactor, n) for n slots. That should be infeasible for
|
||||
// roughly n >= 256, so if this assertion fails, that suggests something is
|
||||
// going wrong.
|
||||
assert(GetTableSize() < 256);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
HyperClockTable::HandleImpl* HyperClockTable::Lookup(
|
||||
const UniqueId64x2& hashed_key) {
|
||||
size_t probe = 0;
|
||||
HandleImpl* e = FindSlot(
|
||||
hashed_key,
|
||||
[&](HandleImpl* h) {
|
||||
@@ -673,7 +822,7 @@ HyperClockTable::HandleImpl* HyperClockTable::Lookup(
|
||||
[&](HandleImpl* h) {
|
||||
return h->displacements.load(std::memory_order_relaxed) == 0;
|
||||
},
|
||||
[&](HandleImpl* /*h*/) {}, probe);
|
||||
[&](HandleImpl* /*h*/, bool /*is_last*/) {});
|
||||
|
||||
return e;
|
||||
}
|
||||
@@ -753,43 +902,19 @@ bool HyperClockTable::Release(HandleImpl* h, bool useful,
|
||||
}
|
||||
}
|
||||
|
||||
void HyperClockTable::Ref(HandleImpl& h) {
|
||||
// Increment acquire counter
|
||||
uint64_t old_meta = h.meta.fetch_add(ClockHandle::kAcquireIncrement,
|
||||
std::memory_order_acquire);
|
||||
|
||||
assert((old_meta >> ClockHandle::kStateShift) &
|
||||
ClockHandle::kStateShareableBit);
|
||||
// Must have already had a reference
|
||||
assert(GetRefcount(old_meta) > 0);
|
||||
(void)old_meta;
|
||||
}
|
||||
|
||||
void HyperClockTable::TEST_RefN(HandleImpl& h, size_t n) {
|
||||
// Increment acquire counter
|
||||
uint64_t old_meta = h.meta.fetch_add(n * ClockHandle::kAcquireIncrement,
|
||||
std::memory_order_acquire);
|
||||
|
||||
assert((old_meta >> ClockHandle::kStateShift) &
|
||||
ClockHandle::kStateShareableBit);
|
||||
(void)old_meta;
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
void HyperClockTable::TEST_ReleaseN(HandleImpl* h, size_t n) {
|
||||
if (n > 0) {
|
||||
// Split into n - 1 and 1 steps.
|
||||
uint64_t old_meta = h->meta.fetch_add(
|
||||
(n - 1) * ClockHandle::kReleaseIncrement, std::memory_order_acquire);
|
||||
assert((old_meta >> ClockHandle::kStateShift) &
|
||||
ClockHandle::kStateShareableBit);
|
||||
(void)old_meta;
|
||||
// Do n-1 simple releases first
|
||||
TEST_ReleaseNMinus1(h, n);
|
||||
|
||||
// Then the last release might be more involved
|
||||
Release(h, /*useful*/ true, /*erase_if_last_ref*/ false);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void HyperClockTable::Erase(const UniqueId64x2& hashed_key) {
|
||||
size_t probe = 0;
|
||||
(void)FindSlot(
|
||||
hashed_key,
|
||||
[&](HandleImpl* h) {
|
||||
@@ -856,48 +981,7 @@ void HyperClockTable::Erase(const UniqueId64x2& hashed_key) {
|
||||
[&](HandleImpl* h) {
|
||||
return h->displacements.load(std::memory_order_relaxed) == 0;
|
||||
},
|
||||
[&](HandleImpl* /*h*/) {}, probe);
|
||||
}
|
||||
|
||||
void HyperClockTable::ConstApplyToEntriesRange(
|
||||
std::function<void(const HandleImpl&)> func, size_t index_begin,
|
||||
size_t index_end, bool apply_if_will_be_deleted) const {
|
||||
uint64_t check_state_mask = ClockHandle::kStateShareableBit;
|
||||
if (!apply_if_will_be_deleted) {
|
||||
check_state_mask |= ClockHandle::kStateVisibleBit;
|
||||
}
|
||||
|
||||
for (size_t i = index_begin; i < index_end; i++) {
|
||||
HandleImpl& h = array_[i];
|
||||
|
||||
// Note: to avoid using compare_exchange, we have to be extra careful.
|
||||
uint64_t old_meta = h.meta.load(std::memory_order_relaxed);
|
||||
// Check if it's an entry visible to lookups
|
||||
if ((old_meta >> ClockHandle::kStateShift) & check_state_mask) {
|
||||
// Increment acquire counter. Note: it's possible that the entry has
|
||||
// completely changed since we loaded old_meta, but incrementing acquire
|
||||
// count is always safe. (Similar to optimistic Lookup here.)
|
||||
old_meta = h.meta.fetch_add(ClockHandle::kAcquireIncrement,
|
||||
std::memory_order_acquire);
|
||||
// Check whether we actually acquired a reference.
|
||||
if ((old_meta >> ClockHandle::kStateShift) &
|
||||
ClockHandle::kStateShareableBit) {
|
||||
// Apply func if appropriate
|
||||
if ((old_meta >> ClockHandle::kStateShift) & check_state_mask) {
|
||||
func(h);
|
||||
}
|
||||
// Pretend we never took the reference
|
||||
h.meta.fetch_sub(ClockHandle::kAcquireIncrement,
|
||||
std::memory_order_release);
|
||||
// No net change, so don't need to check for overflow
|
||||
} else {
|
||||
// For other states, incrementing the acquire counter has no effect
|
||||
// so we don't need to undo it. Furthermore, we cannot safely undo
|
||||
// it because we did not acquire a read reference to lock the
|
||||
// entry in a Shareable state.
|
||||
}
|
||||
}
|
||||
}
|
||||
[&](HandleImpl* /*h*/, bool /*is_last*/) {});
|
||||
}
|
||||
|
||||
void HyperClockTable::EraseUnRefEntries() {
|
||||
@@ -921,10 +1005,10 @@ void HyperClockTable::EraseUnRefEntries() {
|
||||
}
|
||||
}
|
||||
|
||||
template <typename MatchFn, typename AbortFn, typename UpdateFn>
|
||||
inline HyperClockTable::HandleImpl* HyperClockTable::FindSlot(
|
||||
const UniqueId64x2& hashed_key, std::function<bool(HandleImpl*)> match_fn,
|
||||
std::function<bool(HandleImpl*)> abort_fn,
|
||||
std::function<void(HandleImpl*)> update_fn, size_t& probe) {
|
||||
const UniqueId64x2& hashed_key, MatchFn match_fn, AbortFn abort_fn,
|
||||
UpdateFn update_fn) {
|
||||
// NOTE: upper 32 bits of hashed_key[0] is used for sharding
|
||||
//
|
||||
// We use double-hashing probing. Every probe in the sequence is a
|
||||
@@ -938,20 +1022,21 @@ inline HyperClockTable::HandleImpl* HyperClockTable::FindSlot(
|
||||
// TODO: we could also reconsider linear probing, though locality benefits
|
||||
// are limited because each slot is a full cache line
|
||||
size_t increment = static_cast<size_t>(hashed_key[0]) | 1U;
|
||||
size_t current = ModTableSize(base + probe * increment);
|
||||
while (probe <= length_bits_mask_) {
|
||||
size_t first = ModTableSize(base);
|
||||
size_t current = first;
|
||||
bool is_last;
|
||||
do {
|
||||
HandleImpl* h = &array_[current];
|
||||
if (match_fn(h)) {
|
||||
probe++;
|
||||
return h;
|
||||
}
|
||||
if (abort_fn(h)) {
|
||||
return nullptr;
|
||||
}
|
||||
probe++;
|
||||
update_fn(h);
|
||||
current = ModTableSize(current + increment);
|
||||
}
|
||||
is_last = current == first;
|
||||
update_fn(h, is_last);
|
||||
} while (!is_last);
|
||||
// We looped back.
|
||||
return nullptr;
|
||||
}
|
||||
@@ -978,7 +1063,8 @@ inline void HyperClockTable::ReclaimEntryUsage(size_t total_charge) {
|
||||
}
|
||||
|
||||
inline void HyperClockTable::Evict(size_t requested_charge,
|
||||
size_t* freed_charge, size_t* freed_count) {
|
||||
size_t* freed_charge, size_t* freed_count,
|
||||
InsertState&) {
|
||||
// precondition
|
||||
assert(requested_charge > 0);
|
||||
|
||||
@@ -1063,35 +1149,32 @@ void ClockCacheShard<Table>::ApplyToSomeEntries(
|
||||
size_t charge,
|
||||
const Cache::CacheItemHelper* helper)>& callback,
|
||||
size_t average_entries_per_lock, size_t* state) {
|
||||
// The state is essentially going to be the starting hash, which works
|
||||
// nicely even if we resize between calls because we use upper-most
|
||||
// hash bits for table indexes.
|
||||
size_t length_bits = table_.GetLengthBits();
|
||||
// The state will be a simple index into the table. Even with a dynamic
|
||||
// hyper clock cache, entries will generally stay in their existing
|
||||
// slots, so we don't need to be aware of the high-level organization
|
||||
// that makes lookup efficient.
|
||||
size_t length = table_.GetTableSize();
|
||||
|
||||
assert(average_entries_per_lock > 0);
|
||||
// Assuming we are called with same average_entries_per_lock repeatedly,
|
||||
// this simplifies some logic (index_end will not overflow).
|
||||
assert(average_entries_per_lock < length || *state == 0);
|
||||
|
||||
size_t index_begin = *state >> (sizeof(size_t) * 8u - length_bits);
|
||||
size_t index_begin = *state;
|
||||
size_t index_end = index_begin + average_entries_per_lock;
|
||||
if (index_end >= length) {
|
||||
// Going to end.
|
||||
index_end = length;
|
||||
*state = SIZE_MAX;
|
||||
} else {
|
||||
*state = index_end << (sizeof(size_t) * 8u - length_bits);
|
||||
*state = index_end;
|
||||
}
|
||||
|
||||
auto hash_seed = table_.GetHashSeed();
|
||||
table_.ConstApplyToEntriesRange(
|
||||
ConstApplyToEntriesRange(
|
||||
[callback, hash_seed](const HandleImpl& h) {
|
||||
UniqueId64x2 unhashed;
|
||||
callback(ReverseHash(h.hashed_key, &unhashed, hash_seed), h.value,
|
||||
h.GetTotalCharge(), h.helper);
|
||||
},
|
||||
index_begin, index_end, false);
|
||||
table_.HandlePtr(index_begin), table_.HandlePtr(index_end), false);
|
||||
}
|
||||
|
||||
int HyperClockTable::CalcHashBits(
|
||||
@@ -1146,18 +1229,15 @@ Status ClockCacheShard<Table>::Insert(const Slice& key,
|
||||
proto.value = value;
|
||||
proto.helper = helper;
|
||||
proto.total_charge = charge;
|
||||
return table_.Insert(proto, handle, priority,
|
||||
capacity_.load(std::memory_order_relaxed),
|
||||
strict_capacity_limit_.load(std::memory_order_relaxed));
|
||||
return table_.template Insert<Table>(
|
||||
proto, handle, priority, capacity_.load(std::memory_order_relaxed),
|
||||
strict_capacity_limit_.load(std::memory_order_relaxed));
|
||||
}
|
||||
|
||||
template <class Table>
|
||||
typename ClockCacheShard<Table>::HandleImpl*
|
||||
ClockCacheShard<Table>::CreateStandalone(const Slice& key,
|
||||
const UniqueId64x2& hashed_key,
|
||||
Cache::ObjectPtr obj,
|
||||
const Cache::CacheItemHelper* helper,
|
||||
size_t charge, bool allow_uncharged) {
|
||||
typename Table::HandleImpl* ClockCacheShard<Table>::CreateStandalone(
|
||||
const Slice& key, const UniqueId64x2& hashed_key, Cache::ObjectPtr obj,
|
||||
const Cache::CacheItemHelper* helper, size_t charge, bool allow_uncharged) {
|
||||
if (UNLIKELY(key.size() != kCacheKeySize)) {
|
||||
return nullptr;
|
||||
}
|
||||
@@ -1166,7 +1246,7 @@ ClockCacheShard<Table>::CreateStandalone(const Slice& key,
|
||||
proto.value = obj;
|
||||
proto.helper = helper;
|
||||
proto.total_charge = charge;
|
||||
return table_.CreateStandalone(
|
||||
return table_.template CreateStandalone<Table>(
|
||||
proto, capacity_.load(std::memory_order_relaxed),
|
||||
strict_capacity_limit_.load(std::memory_order_relaxed), allow_uncharged);
|
||||
}
|
||||
@@ -1198,6 +1278,7 @@ bool ClockCacheShard<Table>::Release(HandleImpl* handle, bool useful,
|
||||
return table_.Release(handle, useful, erase_if_last_ref);
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
template <class Table>
|
||||
void ClockCacheShard<Table>::TEST_RefN(HandleImpl* h, size_t n) {
|
||||
table_.TEST_RefN(*h, n);
|
||||
@@ -1207,6 +1288,7 @@ template <class Table>
|
||||
void ClockCacheShard<Table>::TEST_ReleaseN(HandleImpl* h, size_t n) {
|
||||
table_.TEST_ReleaseN(h, n);
|
||||
}
|
||||
#endif
|
||||
|
||||
template <class Table>
|
||||
bool ClockCacheShard<Table>::Release(HandleImpl* handle,
|
||||
@@ -1249,7 +1331,7 @@ size_t ClockCacheShard<Table>::GetPinnedUsage() const {
|
||||
size_t table_pinned_usage = 0;
|
||||
const bool charge_metadata =
|
||||
metadata_charge_policy_ == kFullChargeCacheMetadata;
|
||||
table_.ConstApplyToEntriesRange(
|
||||
ConstApplyToEntriesRange(
|
||||
[&table_pinned_usage, charge_metadata](const HandleImpl& h) {
|
||||
uint64_t meta = h.meta.load(std::memory_order_relaxed);
|
||||
uint64_t refcount = GetRefcount(meta);
|
||||
@@ -1262,7 +1344,7 @@ size_t ClockCacheShard<Table>::GetPinnedUsage() const {
|
||||
}
|
||||
}
|
||||
},
|
||||
0, table_.GetTableSize(), true);
|
||||
table_.HandlePtr(0), table_.HandlePtr(table_.GetTableSize()), true);
|
||||
|
||||
return table_pinned_usage + table_.GetStandaloneUsage();
|
||||
}
|
||||
|
||||
Vendored
+157
-116
@@ -371,14 +371,127 @@ struct ClockHandle : public ClockHandleBasicData {
|
||||
static constexpr uint8_t kMaxCountdown = kHighCountdown;
|
||||
// TODO: make these coundown values tuning parameters for eviction?
|
||||
|
||||
// See above
|
||||
std::atomic<uint64_t> meta{};
|
||||
// See above. Mutable for read reference counting.
|
||||
mutable std::atomic<uint64_t> meta{};
|
||||
|
||||
// Anticipating use for SecondaryCache support
|
||||
void* reserved_for_future_use = nullptr;
|
||||
// Whether this is a "deteched" handle that is independently allocated
|
||||
// with `new` (so must be deleted with `delete`).
|
||||
// TODO: ideally this would be packed into some other data field, such
|
||||
// as upper bits of total_charge, but that incurs a measurable performance
|
||||
// regression.
|
||||
bool standalone = false;
|
||||
|
||||
inline bool IsStandalone() const { return standalone; }
|
||||
|
||||
inline void SetStandalone() { standalone = true; }
|
||||
}; // struct ClockHandle
|
||||
|
||||
class HyperClockTable {
|
||||
class BaseClockTable {
|
||||
public:
|
||||
BaseClockTable(CacheMetadataChargePolicy metadata_charge_policy,
|
||||
MemoryAllocator* allocator,
|
||||
const Cache::EvictionCallback* eviction_callback,
|
||||
const uint32_t* hash_seed)
|
||||
: metadata_charge_policy_(metadata_charge_policy),
|
||||
allocator_(allocator),
|
||||
eviction_callback_(*eviction_callback),
|
||||
hash_seed_(*hash_seed) {}
|
||||
|
||||
template <class Table>
|
||||
typename Table::HandleImpl* CreateStandalone(ClockHandleBasicData& proto,
|
||||
size_t capacity,
|
||||
bool strict_capacity_limit,
|
||||
bool allow_uncharged);
|
||||
|
||||
template <class Table>
|
||||
Status Insert(const ClockHandleBasicData& proto,
|
||||
typename Table::HandleImpl** handle, Cache::Priority priority,
|
||||
size_t capacity, bool strict_capacity_limit);
|
||||
|
||||
void Ref(ClockHandle& handle);
|
||||
|
||||
size_t GetOccupancy() const {
|
||||
return occupancy_.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
size_t GetUsage() const { return usage_.load(std::memory_order_relaxed); }
|
||||
|
||||
size_t GetStandaloneUsage() const {
|
||||
return standalone_usage_.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
uint32_t GetHashSeed() const { return hash_seed_; }
|
||||
|
||||
#ifndef NDEBUG
|
||||
// Acquire N references
|
||||
void TEST_RefN(ClockHandle& handle, size_t n);
|
||||
// Helper for TEST_ReleaseN
|
||||
void TEST_ReleaseNMinus1(ClockHandle* handle, size_t n);
|
||||
#endif
|
||||
|
||||
private: // fns
|
||||
// Creates a "standalone" handle for returning from an Insert operation that
|
||||
// cannot be completed by actually inserting into the table.
|
||||
// Updates `standalone_usage_` but not `usage_` nor `occupancy_`.
|
||||
template <class HandleImpl>
|
||||
HandleImpl* StandaloneInsert(const ClockHandleBasicData& proto);
|
||||
|
||||
// Helper for updating `usage_` for new entry with given `total_charge`
|
||||
// and evicting if needed under strict_capacity_limit=true rules. This
|
||||
// means the operation might fail with Status::MemoryLimit. If
|
||||
// `need_evict_for_occupancy`, then eviction of at least one entry is
|
||||
// required, and the operation should fail if not possible.
|
||||
// NOTE: Otherwise, occupancy_ is not managed in this function
|
||||
template <class Table>
|
||||
Status ChargeUsageMaybeEvictStrict(size_t total_charge, size_t capacity,
|
||||
bool need_evict_for_occupancy,
|
||||
typename Table::InsertState& state);
|
||||
|
||||
// Helper for updating `usage_` for new entry with given `total_charge`
|
||||
// and evicting if needed under strict_capacity_limit=false rules. This
|
||||
// means that updating `usage_` always succeeds even if forced to exceed
|
||||
// capacity. If `need_evict_for_occupancy`, then eviction of at least one
|
||||
// entry is required, and the operation should return false if such eviction
|
||||
// is not possible. `usage_` is not updated in that case. Otherwise, returns
|
||||
// true, indicating success.
|
||||
// NOTE: occupancy_ is not managed in this function
|
||||
template <class Table>
|
||||
bool ChargeUsageMaybeEvictNonStrict(size_t total_charge, size_t capacity,
|
||||
bool need_evict_for_occupancy,
|
||||
typename Table::InsertState& state);
|
||||
|
||||
protected: // data
|
||||
// We partition the following members into different cache lines
|
||||
// to avoid false sharing among Lookup, Release, Erase and Insert
|
||||
// operations in ClockCacheShard.
|
||||
|
||||
// Clock algorithm sweep pointer.
|
||||
std::atomic<uint64_t> clock_pointer_{};
|
||||
|
||||
ALIGN_AS(CACHE_LINE_SIZE)
|
||||
// Number of elements in the table.
|
||||
std::atomic<size_t> occupancy_{};
|
||||
|
||||
// Memory usage by entries tracked by the cache (including standalone)
|
||||
std::atomic<size_t> usage_{};
|
||||
|
||||
// Part of usage by standalone entries (not in table)
|
||||
std::atomic<size_t> standalone_usage_{};
|
||||
|
||||
ALIGN_AS(CACHE_LINE_SIZE)
|
||||
const CacheMetadataChargePolicy metadata_charge_policy_;
|
||||
|
||||
// From Cache, for deleter
|
||||
MemoryAllocator* const allocator_;
|
||||
|
||||
// A reference to Cache::eviction_callback_
|
||||
const Cache::EvictionCallback& eviction_callback_;
|
||||
|
||||
// A reference to ShardedCacheBase::hash_seed_
|
||||
const uint32_t& hash_seed_;
|
||||
};
|
||||
|
||||
class HyperClockTable : public BaseClockTable {
|
||||
public:
|
||||
// Target size to be exactly a common cache line size (see static_assert in
|
||||
// clock_cache.cc)
|
||||
@@ -387,16 +500,6 @@ class HyperClockTable {
|
||||
// up in this slot or a higher one.
|
||||
std::atomic<uint32_t> displacements{};
|
||||
|
||||
// Whether this is a "deteched" handle that is independently allocated
|
||||
// with `new` (so must be deleted with `delete`).
|
||||
// TODO: ideally this would be packed into some other data field, such
|
||||
// as upper bits of total_charge, but that incurs a measurable performance
|
||||
// regression.
|
||||
bool standalone = false;
|
||||
|
||||
inline bool IsStandalone() const { return standalone; }
|
||||
|
||||
inline void SetStandalone() { standalone = true; }
|
||||
}; // struct HandleImpl
|
||||
|
||||
struct Opts {
|
||||
@@ -410,49 +513,46 @@ class HyperClockTable {
|
||||
const uint32_t* hash_seed, const Opts& opts);
|
||||
~HyperClockTable();
|
||||
|
||||
Status Insert(const ClockHandleBasicData& proto, HandleImpl** handle,
|
||||
Cache::Priority priority, size_t capacity,
|
||||
bool strict_capacity_limit);
|
||||
// For BaseClockTable::Insert
|
||||
struct InsertState {};
|
||||
|
||||
HandleImpl* CreateStandalone(ClockHandleBasicData& proto, size_t capacity,
|
||||
bool strict_capacity_limit,
|
||||
bool allow_uncharged);
|
||||
void StartInsert(InsertState& state);
|
||||
|
||||
// Returns true iff there is room for the proposed number of entries.
|
||||
bool GrowIfNeeded(size_t new_occupancy, InsertState& state);
|
||||
|
||||
HandleImpl* DoInsert(const ClockHandleBasicData& proto,
|
||||
uint64_t initial_countdown, bool take_ref,
|
||||
InsertState& state);
|
||||
|
||||
// Runs the clock eviction algorithm trying to reclaim at least
|
||||
// requested_charge. Returns how much is evicted, which could be less
|
||||
// if it appears impossible to evict the requested amount without blocking.
|
||||
void Evict(size_t requested_charge, size_t* freed_charge, size_t* freed_count,
|
||||
InsertState& state);
|
||||
|
||||
HandleImpl* Lookup(const UniqueId64x2& hashed_key);
|
||||
|
||||
bool Release(HandleImpl* handle, bool useful, bool erase_if_last_ref);
|
||||
|
||||
void Ref(HandleImpl& handle);
|
||||
|
||||
void Erase(const UniqueId64x2& hashed_key);
|
||||
|
||||
void ConstApplyToEntriesRange(std::function<void(const HandleImpl&)> func,
|
||||
size_t index_begin, size_t index_end,
|
||||
bool apply_if_will_be_deleted) const;
|
||||
|
||||
void EraseUnRefEntries();
|
||||
|
||||
size_t GetTableSize() const { return size_t{1} << length_bits_; }
|
||||
|
||||
int GetLengthBits() const { return length_bits_; }
|
||||
|
||||
size_t GetOccupancy() const {
|
||||
return occupancy_.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
size_t GetOccupancyLimit() const { return occupancy_limit_; }
|
||||
|
||||
size_t GetUsage() const { return usage_.load(std::memory_order_relaxed); }
|
||||
const HandleImpl* HandlePtr(size_t idx) const { return &array_[idx]; }
|
||||
|
||||
size_t GetStandaloneUsage() const {
|
||||
return standalone_usage_.load(std::memory_order_relaxed);
|
||||
#ifndef NDEBUG
|
||||
size_t& TEST_MutableOccupancyLimit() const {
|
||||
return const_cast<size_t&>(occupancy_limit_);
|
||||
}
|
||||
|
||||
uint32_t GetHashSeed() const { return hash_seed_; }
|
||||
|
||||
// Acquire/release N references
|
||||
void TEST_RefN(HandleImpl& handle, size_t n);
|
||||
// Release N references
|
||||
void TEST_ReleaseN(HandleImpl* handle, size_t n);
|
||||
#endif
|
||||
|
||||
private: // functions
|
||||
// Returns x mod 2^{length_bits_}.
|
||||
@@ -460,28 +560,18 @@ class HyperClockTable {
|
||||
return static_cast<size_t>(x) & length_bits_mask_;
|
||||
}
|
||||
|
||||
// Runs the clock eviction algorithm trying to reclaim at least
|
||||
// requested_charge. Returns how much is evicted, which could be less
|
||||
// if it appears impossible to evict the requested amount without blocking.
|
||||
inline void Evict(size_t requested_charge, size_t* freed_charge,
|
||||
size_t* freed_count);
|
||||
|
||||
// Returns the first slot in the probe sequence, starting from the given
|
||||
// probe number, with a handle e such that match(e) is true. At every
|
||||
// step, the function first tests whether match(e) holds. If this is false,
|
||||
// it evaluates abort(e) to decide whether the search should be aborted,
|
||||
// and in the affirmative returns -1. For every handle e probed except
|
||||
// the last one, the function runs update(e).
|
||||
// The probe parameter is modified as follows. We say a probe to a handle
|
||||
// e is aborting if match(e) is false and abort(e) is true. Then the final
|
||||
// value of probe is one more than the last non-aborting probe during the
|
||||
// call. This is so that that the variable can be used to keep track of
|
||||
// progress across consecutive calls to FindSlot.
|
||||
inline HandleImpl* FindSlot(const UniqueId64x2& hashed_key,
|
||||
std::function<bool(HandleImpl*)> match,
|
||||
std::function<bool(HandleImpl*)> stop,
|
||||
std::function<void(HandleImpl*)> update,
|
||||
size_t& probe);
|
||||
// Returns the first slot in the probe sequence with a handle e such that
|
||||
// match_fn(e) is true. At every step, the function first tests whether
|
||||
// match_fn(e) holds. If this is false, it evaluates abort_fn(e) to decide
|
||||
// whether the search should be aborted, and if so, FindSlot immediately
|
||||
// returns nullptr. For every handle e that is not a match and not aborted,
|
||||
// FindSlot runs update_fn(e, is_last) where is_last is set to true iff that
|
||||
// slot will be the last probed because the next would cycle back to the first
|
||||
// slot probed. This function uses templates instead of std::function to
|
||||
// minimize the risk of heap-allocated closures being created.
|
||||
template <typename MatchFn, typename AbortFn, typename UpdateFn>
|
||||
inline HandleImpl* FindSlot(const UniqueId64x2& hashed_key, MatchFn match_fn,
|
||||
AbortFn abort_fn, UpdateFn update_fn);
|
||||
|
||||
// Re-decrement all displacements in probe path starting from beginning
|
||||
// until (not including) the given handle
|
||||
@@ -494,33 +584,6 @@ class HyperClockTable {
|
||||
// before releasing it so that it can be provided to this function.
|
||||
inline void ReclaimEntryUsage(size_t total_charge);
|
||||
|
||||
// Helper for updating `usage_` for new entry with given `total_charge`
|
||||
// and evicting if needed under strict_capacity_limit=true rules. This
|
||||
// means the operation might fail with Status::MemoryLimit. If
|
||||
// `need_evict_for_occupancy`, then eviction of at least one entry is
|
||||
// required, and the operation should fail if not possible.
|
||||
// NOTE: Otherwise, occupancy_ is not managed in this function
|
||||
inline Status ChargeUsageMaybeEvictStrict(size_t total_charge,
|
||||
size_t capacity,
|
||||
bool need_evict_for_occupancy);
|
||||
|
||||
// Helper for updating `usage_` for new entry with given `total_charge`
|
||||
// and evicting if needed under strict_capacity_limit=false rules. This
|
||||
// means that updating `usage_` always succeeds even if forced to exceed
|
||||
// capacity. If `need_evict_for_occupancy`, then eviction of at least one
|
||||
// entry is required, and the operation should return false if such eviction
|
||||
// is not possible. `usage_` is not updated in that case. Otherwise, returns
|
||||
// true, indicating success.
|
||||
// NOTE: occupancy_ is not managed in this function
|
||||
inline bool ChargeUsageMaybeEvictNonStrict(size_t total_charge,
|
||||
size_t capacity,
|
||||
bool need_evict_for_occupancy);
|
||||
|
||||
// Creates a "standalone" handle for returning from an Insert operation that
|
||||
// cannot be completed by actually inserting into the table.
|
||||
// Updates `standalone_usage_` but not `usage_` nor `occupancy_`.
|
||||
inline HandleImpl* StandaloneInsert(const ClockHandleBasicData& proto);
|
||||
|
||||
MemoryAllocator* GetAllocator() const { return allocator_; }
|
||||
|
||||
// Returns the number of bits used to hash an element in the hash
|
||||
@@ -541,33 +604,6 @@ class HyperClockTable {
|
||||
|
||||
// Array of slots comprising the hash table.
|
||||
const std::unique_ptr<HandleImpl[]> array_;
|
||||
|
||||
// From Cache, for deleter
|
||||
MemoryAllocator* const allocator_;
|
||||
|
||||
// A reference to Cache::eviction_callback_
|
||||
const Cache::EvictionCallback& eviction_callback_;
|
||||
|
||||
// A reference to ShardedCacheBase::hash_seed_
|
||||
const uint32_t& hash_seed_;
|
||||
|
||||
// We partition the following members into different cache lines
|
||||
// to avoid false sharing among Lookup, Release, Erase and Insert
|
||||
// operations in ClockCacheShard.
|
||||
|
||||
ALIGN_AS(CACHE_LINE_SIZE)
|
||||
// Clock algorithm sweep pointer.
|
||||
std::atomic<uint64_t> clock_pointer_{};
|
||||
|
||||
ALIGN_AS(CACHE_LINE_SIZE)
|
||||
// Number of elements in the table.
|
||||
std::atomic<size_t> occupancy_{};
|
||||
|
||||
// Memory usage by entries tracked by the cache (including standalone)
|
||||
std::atomic<size_t> usage_{};
|
||||
|
||||
// Part of usage by standalone entries (not in table)
|
||||
std::atomic<size_t> standalone_usage_{};
|
||||
}; // class HyperClockTable
|
||||
|
||||
// A single shard of sharded cache.
|
||||
@@ -666,9 +702,14 @@ class ALIGN_AS(CACHE_LINE_SIZE) ClockCacheShard final : public CacheShardBase {
|
||||
return Lookup(key, hashed_key);
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
size_t& TEST_MutableOccupancyLimit() const {
|
||||
return table_.TEST_MutableOccupancyLimit();
|
||||
}
|
||||
// Acquire/release N references
|
||||
void TEST_RefN(HandleImpl* handle, size_t n);
|
||||
void TEST_ReleaseN(HandleImpl* handle, size_t n);
|
||||
#endif
|
||||
|
||||
private: // data
|
||||
Table table_;
|
||||
|
||||
Vendored
+3
-3
@@ -712,12 +712,12 @@ std::shared_ptr<Cache> LRUCacheOptions::MakeSharedCache() const {
|
||||
return cache;
|
||||
}
|
||||
|
||||
std::shared_ptr<GeneralCache> LRUCacheOptions::MakeSharedGeneralCache() const {
|
||||
std::shared_ptr<RowCache> LRUCacheOptions::MakeSharedRowCache() const {
|
||||
if (secondary_cache) {
|
||||
// Not allowed for a GeneralCache
|
||||
// Not allowed for a RowCache
|
||||
return nullptr;
|
||||
}
|
||||
// Works while GeneralCache is an alias for Cache
|
||||
// Works while RowCache is an alias for Cache
|
||||
return MakeSharedCache();
|
||||
}
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
Vendored
+56
@@ -715,6 +715,62 @@ TEST_F(ClockCacheTest, ClockCounterOverflowTest) {
|
||||
ASSERT_EQ(val.deleted, 1);
|
||||
}
|
||||
|
||||
TEST_F(ClockCacheTest, ClockTableFull) {
|
||||
// Force clock cache table to fill up (not usually allowed) in order
|
||||
// to test full probe sequence that is theoretically possible due to
|
||||
// parallel operations
|
||||
NewShard(6, /*strict_capacity_limit*/ false);
|
||||
size_t size = shard_->GetTableAddressCount();
|
||||
ASSERT_LE(size + 3, 256); // for using char keys
|
||||
// Modify occupancy and capacity limits to attempt insert on full
|
||||
shard_->TEST_MutableOccupancyLimit() = size + 100;
|
||||
shard_->SetCapacity(size + 100);
|
||||
|
||||
DeleteCounter val;
|
||||
std::vector<HandleImpl*> handles;
|
||||
// NOTE: the three extra insertions should create standalone entries
|
||||
for (size_t i = 0; i < size + 3; ++i) {
|
||||
UniqueId64x2 hkey = TestHashedKey(static_cast<char>(i));
|
||||
ASSERT_OK(shard_->Insert(TestKey(hkey), hkey, &val, &kDeleteCounterHelper,
|
||||
1, &handles.emplace_back(),
|
||||
Cache::Priority::HIGH));
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < size + 3; ++i) {
|
||||
UniqueId64x2 hkey = TestHashedKey(static_cast<char>(i));
|
||||
HandleImpl* h = shard_->Lookup(TestKey(hkey), hkey);
|
||||
if (i < size) {
|
||||
ASSERT_NE(h, nullptr);
|
||||
shard_->Release(h);
|
||||
} else {
|
||||
// Standalone entries not visible by lookup
|
||||
ASSERT_EQ(h, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < size + 3; ++i) {
|
||||
ASSERT_NE(handles[i], nullptr);
|
||||
shard_->Release(handles[i]);
|
||||
if (i < size) {
|
||||
// Everything still in cache
|
||||
ASSERT_EQ(val.deleted, 0);
|
||||
} else {
|
||||
// Standalone entries freed on release
|
||||
ASSERT_EQ(val.deleted, i + 1 - size);
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = size + 3; i > 0; --i) {
|
||||
UniqueId64x2 hkey = TestHashedKey(static_cast<char>(i - 1));
|
||||
shard_->Erase(TestKey(hkey), hkey);
|
||||
if (i - 1 > size) {
|
||||
ASSERT_EQ(val.deleted, 3);
|
||||
} else {
|
||||
ASSERT_EQ(val.deleted, 3 + size - (i - 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This test is mostly to exercise some corner case logic, by forcing two
|
||||
// keys to have the same hash, and more
|
||||
TEST_F(ClockCacheTest, CollidingInsertEraseTest) {
|
||||
|
||||
Vendored
+1
-1
@@ -376,7 +376,7 @@ void CacheWithSecondaryAdapter::WaitAll(AsyncLookupHandle* async_handles,
|
||||
for (AsyncLookupHandle* cur : my_pending) {
|
||||
my_secondary_handles.push_back(cur->pending_handle);
|
||||
}
|
||||
secondary_cache_->WaitAll(my_secondary_handles);
|
||||
secondary_cache_->WaitAll(std::move(my_secondary_handles));
|
||||
}
|
||||
|
||||
// Process results
|
||||
|
||||
+27
-6
@@ -15,6 +15,7 @@
|
||||
|
||||
#include "db/blob/blob_file_builder.h"
|
||||
#include "db/compaction/compaction_iterator.h"
|
||||
#include "db/dbformat.h"
|
||||
#include "db/event_helpers.h"
|
||||
#include "db/internal_stats.h"
|
||||
#include "db/merge_helper.h"
|
||||
@@ -205,21 +206,38 @@ 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 strip_timestamp =
|
||||
ts_sz > 0 && !ioptions.persist_user_defined_timestamps;
|
||||
|
||||
std::string key_after_flush_buf;
|
||||
c_iter.SeekToFirst();
|
||||
for (; c_iter.Valid(); c_iter.Next()) {
|
||||
const Slice& key = c_iter.key();
|
||||
const Slice& value = c_iter.value();
|
||||
const ParsedInternalKey& ikey = c_iter.ikey();
|
||||
// Generate a rolling 64-bit hash of the key and values
|
||||
// Note :
|
||||
// Here "key" integrates 'sequence_number'+'kType'+'user key'.
|
||||
s = output_validator.Add(key, value);
|
||||
Slice key_after_flush = key;
|
||||
// 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 (strip_timestamp) {
|
||||
key_after_flush_buf.clear();
|
||||
ReplaceInternalKeyWithMinTimestamp(&key_after_flush_buf, key, ts_sz);
|
||||
key_after_flush = key_after_flush_buf;
|
||||
}
|
||||
|
||||
// Generate a rolling 64-bit hash of the key and values
|
||||
// Note :
|
||||
// Here "key" integrates 'sequence_number'+'kType'+'user key'.
|
||||
s = output_validator.Add(key_after_flush, value);
|
||||
if (!s.ok()) {
|
||||
break;
|
||||
}
|
||||
builder->Add(key, value);
|
||||
builder->Add(key_after_flush, value);
|
||||
|
||||
s = meta->UpdateBoundaries(key, value, ikey.sequence, ikey.type);
|
||||
s = meta->UpdateBoundaries(key_after_flush, value, ikey.sequence,
|
||||
ikey.type);
|
||||
if (!s.ok()) {
|
||||
break;
|
||||
}
|
||||
@@ -244,6 +262,7 @@ Status BuildTable(
|
||||
range_del_it->Next()) {
|
||||
auto tombstone = range_del_it->Tombstone();
|
||||
auto kv = tombstone.Serialize();
|
||||
// TODO(yuzhangyu): handle range deletion for UDT in memtables only.
|
||||
builder->Add(kv.first.Encode(), kv.second);
|
||||
InternalKey tombstone_end = tombstone.SerializeEndKey();
|
||||
meta->UpdateBoundariesForRange(kv.first, tombstone_end, tombstone.seq_,
|
||||
@@ -354,6 +373,8 @@ Status BuildTable(
|
||||
s = *io_status;
|
||||
}
|
||||
|
||||
// TODO(yuzhangyu): handle the key copy in the blob when ts should be
|
||||
// stripped.
|
||||
if (blob_file_builder) {
|
||||
if (s.ok()) {
|
||||
s = blob_file_builder->Finish();
|
||||
|
||||
@@ -1057,6 +1057,36 @@ rocksdb_column_family_handle_t* rocksdb_create_column_family(
|
||||
return handle;
|
||||
}
|
||||
|
||||
rocksdb_column_family_handle_t** rocksdb_create_column_families(
|
||||
rocksdb_t* db, const rocksdb_options_t* column_family_options,
|
||||
int num_column_families, const char* const* column_family_names,
|
||||
size_t* lencfs, char** errptr) {
|
||||
std::vector<ColumnFamilyHandle*> handles;
|
||||
std::vector<std::string> names;
|
||||
for (int i = 0; i != num_column_families; ++i) {
|
||||
names.push_back(std::string(column_family_names[i]));
|
||||
}
|
||||
SaveError(errptr, db->rep->CreateColumnFamilies(
|
||||
ColumnFamilyOptions(column_family_options->rep), names,
|
||||
&handles));
|
||||
|
||||
*lencfs = handles.size();
|
||||
rocksdb_column_family_handle_t** c_handles =
|
||||
static_cast<rocksdb_column_family_handle_t**>(
|
||||
malloc(sizeof(rocksdb_column_family_handle_t*) * handles.size()));
|
||||
for (size_t i = 0; i != handles.size(); ++i) {
|
||||
c_handles[i] = new rocksdb_column_family_handle_t;
|
||||
c_handles[i]->rep = handles[i];
|
||||
}
|
||||
|
||||
return c_handles;
|
||||
}
|
||||
|
||||
void rocksdb_create_column_families_destroy(
|
||||
rocksdb_column_family_handle_t** list) {
|
||||
free(list);
|
||||
}
|
||||
|
||||
rocksdb_column_family_handle_t* rocksdb_create_column_family_with_ttl(
|
||||
rocksdb_t* db, const rocksdb_options_t* column_family_options,
|
||||
const char* column_family_name, int ttl, char** errptr) {
|
||||
@@ -3740,16 +3770,21 @@ void rocksdb_options_set_hash_link_list_rep(rocksdb_options_t* opt,
|
||||
ROCKSDB_NAMESPACE::NewHashLinkListRepFactory(bucket_count));
|
||||
}
|
||||
|
||||
void rocksdb_options_set_plain_table_factory(rocksdb_options_t* opt,
|
||||
uint32_t user_key_len,
|
||||
int bloom_bits_per_key,
|
||||
double hash_table_ratio,
|
||||
size_t index_sparseness) {
|
||||
void rocksdb_options_set_plain_table_factory(
|
||||
rocksdb_options_t* opt, uint32_t user_key_len, int bloom_bits_per_key,
|
||||
double hash_table_ratio, size_t index_sparseness, size_t huge_page_tlb_size,
|
||||
char encoding_type, unsigned char full_scan_mode,
|
||||
unsigned char store_index_in_file) {
|
||||
ROCKSDB_NAMESPACE::PlainTableOptions options;
|
||||
options.user_key_len = user_key_len;
|
||||
options.bloom_bits_per_key = bloom_bits_per_key;
|
||||
options.hash_table_ratio = hash_table_ratio;
|
||||
options.index_sparseness = index_sparseness;
|
||||
options.huge_page_tlb_size = huge_page_tlb_size;
|
||||
options.encoding_type =
|
||||
static_cast<ROCKSDB_NAMESPACE::EncodingType>(encoding_type);
|
||||
options.full_scan_mode = full_scan_mode;
|
||||
options.store_index_in_file = store_index_in_file;
|
||||
|
||||
ROCKSDB_NAMESPACE::TableFactory* factory =
|
||||
ROCKSDB_NAMESPACE::NewPlainTableFactory(options);
|
||||
@@ -5588,6 +5623,23 @@ int rocksdb_transactiondb_property_int(rocksdb_transactiondb_t* db,
|
||||
}
|
||||
}
|
||||
|
||||
rocksdb_t* rocksdb_transactiondb_get_base_db(
|
||||
rocksdb_transactiondb_t* txn_db) {
|
||||
DB* base_db = txn_db->rep->GetBaseDB();
|
||||
|
||||
if (base_db != nullptr) {
|
||||
rocksdb_t* result = new rocksdb_t;
|
||||
result->rep = base_db;
|
||||
return result;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void rocksdb_transactiondb_close_base_db(rocksdb_t* base_db) {
|
||||
delete base_db;
|
||||
}
|
||||
|
||||
rocksdb_transaction_t* rocksdb_transaction_begin(
|
||||
rocksdb_transactiondb_t* txn_db,
|
||||
const rocksdb_writeoptions_t* write_options,
|
||||
|
||||
+26
-3
@@ -1674,7 +1674,8 @@ int main(int argc, char** argv) {
|
||||
rocksdb_options_set_prefix_extractor(
|
||||
options, rocksdb_slicetransform_create_fixed_prefix(3));
|
||||
rocksdb_options_set_hash_skip_list_rep(options, 5000, 4, 4);
|
||||
rocksdb_options_set_plain_table_factory(options, 4, 10, 0.75, 16);
|
||||
rocksdb_options_set_plain_table_factory(options, 4, 10, 0.75, 16, 0, 0, 0,
|
||||
0);
|
||||
rocksdb_options_set_allow_concurrent_memtable_write(options, 0);
|
||||
|
||||
db = rocksdb_open(options, dbname, &err);
|
||||
@@ -3121,6 +3122,17 @@ int main(int argc, char** argv) {
|
||||
CheckTxnDBGetCF(txn_db, roptions, cfh, "cf_foo", NULL);
|
||||
CheckTxnDBPinGetCF(txn_db, roptions, cfh, "cf_foo", NULL);
|
||||
|
||||
|
||||
//memory usage
|
||||
rocksdb_t* base_db = rocksdb_transactiondb_get_base_db(txn_db);
|
||||
rocksdb_memory_consumers_t* consumers = rocksdb_memory_consumers_create();
|
||||
rocksdb_memory_consumers_add_db(consumers, base_db);
|
||||
rocksdb_memory_usage_t* usage = rocksdb_approximate_memory_usage_create(consumers, &err);
|
||||
CheckNoError(err);
|
||||
rocksdb_approximate_memory_usage_destroy(usage);
|
||||
rocksdb_memory_consumers_destroy(consumers);
|
||||
rocksdb_transactiondb_close_base_db(base_db);
|
||||
|
||||
// flush
|
||||
rocksdb_flushoptions_t* flush_options = rocksdb_flushoptions_create();
|
||||
rocksdb_flushoptions_set_wait(flush_options, 1);
|
||||
@@ -3376,8 +3388,19 @@ int main(int argc, char** argv) {
|
||||
rocksdb_put(db, woptions, "key", 3, "value", 5, &err);
|
||||
CheckNoError(err);
|
||||
rocksdb_column_family_handle_t *cfh1, *cfh2;
|
||||
cfh1 = rocksdb_create_column_family(db, db_options, "txn_db_cf1", &err);
|
||||
cfh2 = rocksdb_create_column_family(db, db_options, "txn_db_cf2", &err);
|
||||
char** list_const_cf_names = (char**)malloc(2 * sizeof(char*));
|
||||
list_const_cf_names[0] = "txn_db_cf1";
|
||||
list_const_cf_names[1] = "txn_db_cf2";
|
||||
size_t cflen;
|
||||
rocksdb_column_family_handle_t** list_cfh = rocksdb_create_column_families(
|
||||
db, db_options, 2, (const char* const*)list_const_cf_names, &cflen,
|
||||
&err);
|
||||
free(list_const_cf_names);
|
||||
CheckNoError(err);
|
||||
assert(cflen == 2);
|
||||
cfh1 = list_cfh[0];
|
||||
cfh2 = list_cfh[1];
|
||||
rocksdb_create_column_families_destroy(list_cfh);
|
||||
txn = rocksdb_optimistictransaction_begin(otxn_db, woptions, otxn_options,
|
||||
NULL);
|
||||
rocksdb_transaction_put_cf(txn, cfh1, "key_cf1", 7, "val_cf1", 7, &err);
|
||||
|
||||
+8
-12
@@ -382,8 +382,8 @@ ColumnFamilyOptions SanitizeOptions(const ImmutableDBOptions& db_options,
|
||||
|
||||
const uint64_t kAdjustedTtl = 30 * 24 * 60 * 60;
|
||||
if (result.ttl == kDefaultTtl) {
|
||||
if (is_block_based_table &&
|
||||
result.compaction_style != kCompactionStyleFIFO) {
|
||||
if (is_block_based_table) {
|
||||
// For FIFO, max_open_files is checked in ValidateOptions().
|
||||
result.ttl = kAdjustedTtl;
|
||||
} else {
|
||||
result.ttl = 0;
|
||||
@@ -403,16 +403,12 @@ ColumnFamilyOptions SanitizeOptions(const ImmutableDBOptions& db_options,
|
||||
result.periodic_compaction_seconds = kAdjustedPeriodicCompSecs;
|
||||
}
|
||||
} else {
|
||||
// result.compaction_style == kCompactionStyleFIFO
|
||||
if (result.ttl == 0) {
|
||||
if (is_block_based_table) {
|
||||
if (result.periodic_compaction_seconds == kDefaultPeriodicCompSecs) {
|
||||
result.periodic_compaction_seconds = kAdjustedPeriodicCompSecs;
|
||||
}
|
||||
result.ttl = result.periodic_compaction_seconds;
|
||||
}
|
||||
} else if (result.periodic_compaction_seconds != 0) {
|
||||
result.ttl = std::min(result.ttl, result.periodic_compaction_seconds);
|
||||
if (result.periodic_compaction_seconds != kDefaultPeriodicCompSecs &&
|
||||
result.periodic_compaction_seconds > 0) {
|
||||
ROCKS_LOG_WARN(
|
||||
db_options.info_log.get(),
|
||||
"periodic_compaction_seconds does not support FIFO compaction. You"
|
||||
"may want to set option TTL instead.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,14 +23,13 @@ namespace ROCKSDB_NAMESPACE {
|
||||
const uint64_t kRangeTombstoneSentinel =
|
||||
PackSequenceAndType(kMaxSequenceNumber, kTypeRangeDeletion);
|
||||
|
||||
int sstableKeyCompare(const Comparator* user_cmp, const InternalKey& a,
|
||||
const InternalKey& b) {
|
||||
auto c = user_cmp->CompareWithoutTimestamp(a.user_key(), b.user_key());
|
||||
int sstableKeyCompare(const Comparator* uc, const Slice& a, const Slice& b) {
|
||||
auto c = uc->CompareWithoutTimestamp(ExtractUserKey(a), ExtractUserKey(b));
|
||||
if (c != 0) {
|
||||
return c;
|
||||
}
|
||||
auto a_footer = ExtractInternalKeyFooter(a.Encode());
|
||||
auto b_footer = ExtractInternalKeyFooter(b.Encode());
|
||||
auto a_footer = ExtractInternalKeyFooter(a);
|
||||
auto b_footer = ExtractInternalKeyFooter(b);
|
||||
if (a_footer == kRangeTombstoneSentinel) {
|
||||
if (b_footer != kRangeTombstoneSentinel) {
|
||||
return -1;
|
||||
|
||||
@@ -31,8 +31,19 @@ namespace ROCKSDB_NAMESPACE {
|
||||
// that key never appears in the database. We don't want adjacent sstables to
|
||||
// be considered overlapping if they are separated by the range tombstone
|
||||
// sentinel.
|
||||
int sstableKeyCompare(const Comparator* user_cmp, const InternalKey& a,
|
||||
const InternalKey& b);
|
||||
int sstableKeyCompare(const Comparator* user_cmp, const Slice&, const Slice&);
|
||||
inline int sstableKeyCompare(const Comparator* user_cmp, const Slice& a,
|
||||
const InternalKey& b) {
|
||||
return sstableKeyCompare(user_cmp, a, b.Encode());
|
||||
}
|
||||
inline int sstableKeyCompare(const Comparator* user_cmp, const InternalKey& a,
|
||||
const Slice& b) {
|
||||
return sstableKeyCompare(user_cmp, a.Encode(), b);
|
||||
}
|
||||
inline int sstableKeyCompare(const Comparator* user_cmp, const InternalKey& a,
|
||||
const InternalKey& b) {
|
||||
return sstableKeyCompare(user_cmp, a.Encode(), b.Encode());
|
||||
}
|
||||
int sstableKeyCompare(const Comparator* user_cmp, const InternalKey* a,
|
||||
const InternalKey& b);
|
||||
int sstableKeyCompare(const Comparator* user_cmp, const InternalKey& a,
|
||||
|
||||
@@ -1096,17 +1096,17 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
// GenSubcompactionBoundaries doesn't strip away the timestamp.
|
||||
size_t ts_sz = cfd->user_comparator()->timestamp_size();
|
||||
if (start.has_value()) {
|
||||
read_options.iterate_lower_bound = &start.value();
|
||||
read_options.iterate_lower_bound = &(*start);
|
||||
if (ts_sz > 0) {
|
||||
start_without_ts = StripTimestampFromUserKey(start.value(), ts_sz);
|
||||
read_options.iterate_lower_bound = &start_without_ts.value();
|
||||
start_without_ts = StripTimestampFromUserKey(*start, ts_sz);
|
||||
read_options.iterate_lower_bound = &(*start_without_ts);
|
||||
}
|
||||
}
|
||||
if (end.has_value()) {
|
||||
read_options.iterate_upper_bound = &end.value();
|
||||
read_options.iterate_upper_bound = &(*end);
|
||||
if (ts_sz > 0) {
|
||||
end_without_ts = StripTimestampFromUserKey(end.value(), ts_sz);
|
||||
read_options.iterate_upper_bound = &end_without_ts.value();
|
||||
end_without_ts = StripTimestampFromUserKey(*end, ts_sz);
|
||||
read_options.iterate_upper_bound = &(*end_without_ts);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1138,8 +1138,7 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
}
|
||||
|
||||
if (start.has_value()) {
|
||||
start_ikey.SetInternalKey(start.value(), kMaxSequenceNumber,
|
||||
kValueTypeForSeek);
|
||||
start_ikey.SetInternalKey(*start, kMaxSequenceNumber, kValueTypeForSeek);
|
||||
if (ts_sz > 0) {
|
||||
start_ikey.UpdateInternalKey(kMaxSequenceNumber, kValueTypeForSeek,
|
||||
&ts_slice);
|
||||
@@ -1148,7 +1147,7 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
start_user_key = start_ikey.GetUserKey();
|
||||
}
|
||||
if (end.has_value()) {
|
||||
end_ikey.SetInternalKey(end.value(), kMaxSequenceNumber, kValueTypeForSeek);
|
||||
end_ikey.SetInternalKey(*end, kMaxSequenceNumber, kValueTypeForSeek);
|
||||
if (ts_sz > 0) {
|
||||
end_ikey.UpdateInternalKey(kMaxSequenceNumber, kValueTypeForSeek,
|
||||
&ts_slice);
|
||||
@@ -1289,8 +1288,8 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
while (status.ok() && !cfd->IsDropped() && c_iter->Valid()) {
|
||||
// Invariant: c_iter.status() is guaranteed to be OK if c_iter->Valid()
|
||||
// returns true.
|
||||
assert(!end.has_value() || cfd->user_comparator()->Compare(
|
||||
c_iter->user_key(), end.value()) < 0);
|
||||
assert(!end.has_value() ||
|
||||
cfd->user_comparator()->Compare(c_iter->user_key(), *end) < 0);
|
||||
|
||||
if (c_iter_stats.num_input_records % kRecordStatsEvery ==
|
||||
kRecordStatsEvery - 1) {
|
||||
@@ -1818,10 +1817,10 @@ Status CompactionJob::OpenCompactionOutputFile(SubcompactionState* sub_compact,
|
||||
uint64_t current_time = static_cast<uint64_t>(temp_current_time);
|
||||
InternalKey tmp_start, tmp_end;
|
||||
if (sub_compact->start.has_value()) {
|
||||
tmp_start.SetMinPossibleForUserKey(sub_compact->start.value());
|
||||
tmp_start.SetMinPossibleForUserKey(*(sub_compact->start));
|
||||
}
|
||||
if (sub_compact->end.has_value()) {
|
||||
tmp_end.SetMinPossibleForUserKey(sub_compact->end.value());
|
||||
tmp_end.SetMinPossibleForUserKey(*(sub_compact->end));
|
||||
}
|
||||
uint64_t oldest_ancester_time =
|
||||
sub_compact->compaction->MinInputFileOldestAncesterTime(
|
||||
|
||||
@@ -127,11 +127,6 @@ size_t CompactionOutputs::UpdateGrandparentBoundaryInfo(
|
||||
if (grandparents.empty()) {
|
||||
return curr_key_boundary_switched_num;
|
||||
}
|
||||
assert(!internal_key.empty());
|
||||
InternalKey ikey;
|
||||
ikey.DecodeFrom(internal_key);
|
||||
assert(ikey.Valid());
|
||||
|
||||
const Comparator* ucmp = compaction_->column_family_data()->user_comparator();
|
||||
|
||||
// Move the grandparent_index_ to the file containing the current user_key.
|
||||
@@ -139,7 +134,7 @@ size_t CompactionOutputs::UpdateGrandparentBoundaryInfo(
|
||||
// index points to the last file containing the key.
|
||||
while (grandparent_index_ < grandparents.size()) {
|
||||
if (being_grandparent_gap_) {
|
||||
if (sstableKeyCompare(ucmp, ikey,
|
||||
if (sstableKeyCompare(ucmp, internal_key,
|
||||
grandparents[grandparent_index_]->smallest) < 0) {
|
||||
break;
|
||||
}
|
||||
@@ -152,13 +147,13 @@ size_t CompactionOutputs::UpdateGrandparentBoundaryInfo(
|
||||
being_grandparent_gap_ = false;
|
||||
} else {
|
||||
int cmp_result = sstableKeyCompare(
|
||||
ucmp, ikey, grandparents[grandparent_index_]->largest);
|
||||
ucmp, internal_key, grandparents[grandparent_index_]->largest);
|
||||
// If it's same key, make sure grandparent_index_ is pointing to the last
|
||||
// one.
|
||||
if (cmp_result < 0 ||
|
||||
(cmp_result == 0 &&
|
||||
(grandparent_index_ == grandparents.size() - 1 ||
|
||||
sstableKeyCompare(ucmp, ikey,
|
||||
sstableKeyCompare(ucmp, internal_key,
|
||||
grandparents[grandparent_index_ + 1]->smallest) <
|
||||
0))) {
|
||||
break;
|
||||
|
||||
@@ -206,10 +206,10 @@ class CompactionOutputs {
|
||||
// We may only split the output when the cursor is in the range. Split
|
||||
if ((!end.has_value() ||
|
||||
icmp->user_comparator()->Compare(
|
||||
ExtractUserKey(output_split_key->Encode()), end.value()) < 0) &&
|
||||
(!start.has_value() || icmp->user_comparator()->Compare(
|
||||
ExtractUserKey(output_split_key->Encode()),
|
||||
start.value()) > 0)) {
|
||||
ExtractUserKey(output_split_key->Encode()), *end) < 0) &&
|
||||
(!start.has_value() ||
|
||||
icmp->user_comparator()->Compare(
|
||||
ExtractUserKey(output_split_key->Encode()), *start) > 0)) {
|
||||
local_output_split_key_ = output_split_key;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1968,6 +1968,15 @@ TEST_F(CompactionPickerTest, OverlappingUserKeys11) {
|
||||
ASSERT_EQ(7U, compaction->input(1, 0)->fd.GetNumber());
|
||||
}
|
||||
|
||||
TEST_F(CompactionPickerTest, FileTtlBoosterLargeNumLevels) {
|
||||
const uint64_t kCurrentTime = 1000000;
|
||||
FileTtlBooster booster(kCurrentTime, /*ttl=*/2048,
|
||||
/*num_non_empty_levels=*/100, /*level=*/1);
|
||||
FileMetaData meta;
|
||||
meta.oldest_ancester_time = kCurrentTime - 1023;
|
||||
ASSERT_EQ(1, booster.GetBoostScore(&meta));
|
||||
}
|
||||
|
||||
TEST_F(CompactionPickerTest, FileTtlBooster) {
|
||||
// Set TTL to 2048
|
||||
// TTL boosting for all levels starts at 1024,
|
||||
|
||||
@@ -53,8 +53,10 @@ class FileTtlBooster {
|
||||
enabled_ = true;
|
||||
uint64_t all_boost_start_age = ttl / 2;
|
||||
uint64_t all_boost_age_range = (ttl / 32) * 31 - all_boost_start_age;
|
||||
// TODO(cbi): more reasonable algorithm that gives different values
|
||||
// when num_non_empty_levels - level - 1 > 63.
|
||||
uint64_t boost_age_range =
|
||||
all_boost_age_range >> (num_non_empty_levels - level - 1);
|
||||
all_boost_age_range >> std::min(63, num_non_empty_levels - level - 1);
|
||||
boost_age_start_ = all_boost_start_age + boost_age_range;
|
||||
const uint64_t kBoostRatio = 16;
|
||||
// prevent 0 value to avoid divide 0 error.
|
||||
|
||||
@@ -4600,6 +4600,7 @@ TEST_F(DBCompactionTest, LevelTtlCascadingCompactions) {
|
||||
PutVarint64(encoded_fieled, 0);
|
||||
}
|
||||
});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
options.env = env_;
|
||||
|
||||
@@ -4616,7 +4617,6 @@ TEST_F(DBCompactionTest, LevelTtlCascadingCompactions) {
|
||||
ttl_compactions++;
|
||||
}
|
||||
});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
// Add two L6 files with key ranges: [1 .. 100], [101 .. 200].
|
||||
Random rnd(301);
|
||||
@@ -4748,6 +4748,7 @@ TEST_F(DBCompactionTest, LevelPeriodicCompaction) {
|
||||
PutVarint64(encoded_fieled, 0);
|
||||
}
|
||||
});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
options.env = env_;
|
||||
|
||||
@@ -4764,7 +4765,6 @@ TEST_F(DBCompactionTest, LevelPeriodicCompaction) {
|
||||
periodic_compactions++;
|
||||
}
|
||||
});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
Random rnd(301);
|
||||
for (int i = 0; i < kNumLevelFiles; ++i) {
|
||||
|
||||
@@ -852,8 +852,8 @@ void DBImpl::NotifyOnFlushBegin(ColumnFamilyData* cfd, FileMetaData* file_meta,
|
||||
}
|
||||
}
|
||||
mutex_.Lock();
|
||||
// no need to signal bg_cv_ as it will be signaled at the end of the
|
||||
// flush process.
|
||||
// no need to signal bg_cv_ as it will be signaled at the end of the
|
||||
// flush process.
|
||||
}
|
||||
|
||||
void DBImpl::NotifyOnFlushCompleted(
|
||||
@@ -2775,7 +2775,7 @@ ColumnFamilyData* DBImpl::PopFirstFromCompactionQueue() {
|
||||
|
||||
DBImpl::FlushRequest DBImpl::PopFirstFromFlushQueue() {
|
||||
assert(!flush_queue_.empty());
|
||||
FlushRequest flush_req = flush_queue_.front();
|
||||
FlushRequest flush_req = std::move(flush_queue_.front());
|
||||
flush_queue_.pop_front();
|
||||
if (!immutable_db_options_.atomic_flush) {
|
||||
assert(flush_req.cfd_to_max_mem_id_to_persist.size() == 1);
|
||||
@@ -2970,14 +2970,12 @@ Status DBImpl::BackgroundFlush(bool* made_progress, JobContext* job_context,
|
||||
autovector<ColumnFamilyData*> column_families_not_to_flush;
|
||||
while (!flush_queue_.empty()) {
|
||||
// This cfd is already referenced
|
||||
const FlushRequest& flush_req = PopFirstFromFlushQueue();
|
||||
FlushReason flush_reason = flush_req.flush_reason;
|
||||
auto [flush_reason, cfd_to_max_mem_id_to_persist] =
|
||||
PopFirstFromFlushQueue();
|
||||
superversion_contexts.clear();
|
||||
superversion_contexts.reserve(
|
||||
flush_req.cfd_to_max_mem_id_to_persist.size());
|
||||
superversion_contexts.reserve(cfd_to_max_mem_id_to_persist.size());
|
||||
|
||||
for (const auto& iter : flush_req.cfd_to_max_mem_id_to_persist) {
|
||||
ColumnFamilyData* cfd = iter.first;
|
||||
for (const auto& [cfd, max_memtable_id] : cfd_to_max_mem_id_to_persist) {
|
||||
if (cfd->GetMempurgeUsed()) {
|
||||
// If imm() contains silent memtables (e.g.: because
|
||||
// MemPurge was activated), requesting a flush will
|
||||
@@ -2991,7 +2989,7 @@ Status DBImpl::BackgroundFlush(bool* made_progress, JobContext* job_context,
|
||||
continue;
|
||||
}
|
||||
superversion_contexts.emplace_back(SuperVersionContext(true));
|
||||
bg_flush_args.emplace_back(cfd, iter.second,
|
||||
bg_flush_args.emplace_back(cfd, max_memtable_id,
|
||||
&(superversion_contexts.back()), flush_reason);
|
||||
}
|
||||
if (!bg_flush_args.empty()) {
|
||||
|
||||
@@ -1655,6 +1655,7 @@ Status DBImpl::WriteLevel0TableForRecovery(int job_id, ColumnFamilyData* cfd,
|
||||
Version* version = cfd->current();
|
||||
version->Ref();
|
||||
const ReadOptions read_option(Env::IOActivity::kDBOpen);
|
||||
uint64_t num_input_entries = 0;
|
||||
s = BuildTable(
|
||||
dbname_, versions_.get(), immutable_db_options_, tboptions,
|
||||
file_options_for_compaction_, read_option, cfd->table_cache(),
|
||||
@@ -1664,7 +1665,8 @@ Status DBImpl::WriteLevel0TableForRecovery(int job_id, ColumnFamilyData* cfd,
|
||||
io_tracer_, BlobFileCreationReason::kRecovery,
|
||||
empty_seqno_time_mapping, &event_logger_, job_id, Env::IO_HIGH,
|
||||
nullptr /* table_properties */, write_hint,
|
||||
nullptr /*full_history_ts_low*/, &blob_callback_, version);
|
||||
nullptr /*full_history_ts_low*/, &blob_callback_, version,
|
||||
&num_input_entries);
|
||||
version->Unref();
|
||||
LogFlush(immutable_db_options_.info_log);
|
||||
ROCKS_LOG_DEBUG(immutable_db_options_.info_log,
|
||||
@@ -1678,6 +1680,19 @@ Status DBImpl::WriteLevel0TableForRecovery(int job_id, ColumnFamilyData* cfd,
|
||||
if (!io_s.ok() && s.ok()) {
|
||||
s = io_s;
|
||||
}
|
||||
|
||||
uint64_t total_num_entries = mem->num_entries();
|
||||
if (s.ok() && total_num_entries != num_input_entries) {
|
||||
std::string msg = "Expected " + std::to_string(total_num_entries) +
|
||||
" entries in memtable, but read " +
|
||||
std::to_string(num_input_entries);
|
||||
ROCKS_LOG_WARN(immutable_db_options_.info_log,
|
||||
"[%s] [JOB %d] Level-0 flush during recover: %s",
|
||||
cfd->GetName().c_str(), job_id, msg.c_str());
|
||||
if (immutable_db_options_.flush_verify_memtable_count) {
|
||||
s = Status::Corruption(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ReleaseFileNumberFromPendingOutputs(pending_outputs_inserted_elem);
|
||||
|
||||
@@ -880,9 +880,13 @@ TEST_F(DBOptionsTest, SanitizeFIFOPeriodicCompaction) {
|
||||
Options options;
|
||||
options.compaction_style = kCompactionStyleFIFO;
|
||||
options.env = CurrentOptions().env;
|
||||
// Default value allows RocksDB to set ttl to 30 days.
|
||||
ASSERT_EQ(30 * 24 * 60 * 60, dbfull()->GetOptions().ttl);
|
||||
|
||||
// Disable
|
||||
options.ttl = 0;
|
||||
Reopen(options);
|
||||
ASSERT_EQ(30 * 24 * 60 * 60, dbfull()->GetOptions().ttl);
|
||||
ASSERT_EQ(0, dbfull()->GetOptions().ttl);
|
||||
|
||||
options.ttl = 100;
|
||||
Reopen(options);
|
||||
@@ -892,15 +896,12 @@ TEST_F(DBOptionsTest, SanitizeFIFOPeriodicCompaction) {
|
||||
Reopen(options);
|
||||
ASSERT_EQ(100 * 24 * 60 * 60, dbfull()->GetOptions().ttl);
|
||||
|
||||
options.ttl = 200;
|
||||
options.periodic_compaction_seconds = 300;
|
||||
Reopen(options);
|
||||
ASSERT_EQ(200, dbfull()->GetOptions().ttl);
|
||||
|
||||
// periodic_compaction_seconds should have no effect
|
||||
// on FIFO compaction.
|
||||
options.ttl = 500;
|
||||
options.periodic_compaction_seconds = 300;
|
||||
Reopen(options);
|
||||
ASSERT_EQ(300, dbfull()->GetOptions().ttl);
|
||||
ASSERT_EQ(500, dbfull()->GetOptions().ttl);
|
||||
}
|
||||
|
||||
TEST_F(DBOptionsTest, SetFIFOCompactionOptions) {
|
||||
|
||||
+7
-7
@@ -6902,16 +6902,16 @@ TEST_F(DBTest, RowCache) {
|
||||
options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
|
||||
LRUCacheOptions cache_options;
|
||||
cache_options.capacity = 8192;
|
||||
options.row_cache = cache_options.MakeSharedGeneralCache();
|
||||
options.row_cache = cache_options.MakeSharedRowCache();
|
||||
// BEGIN check that Cache classes as aliases of each other.
|
||||
// Currently, GeneralCache and BlockCache are aliases for Cache.
|
||||
// Currently, RowCache and BlockCache are aliases for Cache.
|
||||
// This is expected to change (carefully, intentionally)
|
||||
std::shared_ptr<GeneralCache> general_cache = options.row_cache;
|
||||
std::shared_ptr<Cache> cache = general_cache;
|
||||
std::shared_ptr<BlockCache> block_cache = general_cache;
|
||||
general_cache = cache;
|
||||
std::shared_ptr<RowCache> row_cache = options.row_cache;
|
||||
std::shared_ptr<Cache> cache = row_cache;
|
||||
std::shared_ptr<BlockCache> block_cache = row_cache;
|
||||
row_cache = cache;
|
||||
block_cache = cache;
|
||||
general_cache = block_cache;
|
||||
row_cache = block_cache;
|
||||
cache = block_cache;
|
||||
// END check that Cache classes as aliases of each other.
|
||||
DestroyAndReopen(options);
|
||||
|
||||
+134
-26
@@ -311,21 +311,32 @@ TEST_F(DBWALTest, Recover) {
|
||||
} while (ChangeWalOptions());
|
||||
}
|
||||
|
||||
class DBWALTestWithTimestamp : public DBBasicTestWithTimestampBase {
|
||||
class DBWALTestWithTimestamp
|
||||
: public DBBasicTestWithTimestampBase,
|
||||
public testing::WithParamInterface<test::UserDefinedTimestampTestMode> {
|
||||
public:
|
||||
DBWALTestWithTimestamp()
|
||||
: DBBasicTestWithTimestampBase("db_wal_test_with_timestamp") {}
|
||||
|
||||
void SetUp() override {
|
||||
persist_udt_ = test::ShouldPersistUDT(GetParam());
|
||||
DBBasicTestWithTimestampBase::SetUp();
|
||||
}
|
||||
|
||||
Status CreateAndReopenWithCFWithTs(const std::vector<std::string>& cfs,
|
||||
const Options& options) {
|
||||
const Options& options,
|
||||
bool avoid_flush_during_recovery = false) {
|
||||
CreateColumnFamilies(cfs, options);
|
||||
return ReopenColumnFamiliesWithTs(cfs, options);
|
||||
return ReopenColumnFamiliesWithTs(cfs, options,
|
||||
avoid_flush_during_recovery);
|
||||
}
|
||||
|
||||
Status ReopenColumnFamiliesWithTs(const std::vector<std::string>& cfs,
|
||||
Options ts_options) {
|
||||
Options ts_options,
|
||||
bool avoid_flush_during_recovery = false) {
|
||||
Options default_options = CurrentOptions();
|
||||
default_options.create_if_missing = false;
|
||||
default_options.avoid_flush_during_recovery = avoid_flush_during_recovery;
|
||||
ts_options.create_if_missing = false;
|
||||
|
||||
std::vector<Options> cf_options(cfs.size(), ts_options);
|
||||
@@ -343,46 +354,88 @@ class DBWALTestWithTimestamp : public DBBasicTestWithTimestampBase {
|
||||
}
|
||||
|
||||
void CheckGet(const ReadOptions& read_opts, uint32_t cf, const Slice& key,
|
||||
const std::string& expected_value) {
|
||||
const std::string& expected_value,
|
||||
const std::string& expected_ts) {
|
||||
std::string actual_value;
|
||||
ASSERT_OK(db_->Get(read_opts, handles_[cf], key, &actual_value));
|
||||
std::string actual_ts;
|
||||
ASSERT_OK(
|
||||
db_->Get(read_opts, handles_[cf], key, &actual_value, &actual_ts));
|
||||
ASSERT_EQ(expected_value, actual_value);
|
||||
ASSERT_EQ(expected_ts, actual_ts);
|
||||
}
|
||||
|
||||
protected:
|
||||
bool persist_udt_;
|
||||
};
|
||||
|
||||
TEST_F(DBWALTestWithTimestamp, Recover) {
|
||||
TEST_P(DBWALTestWithTimestamp, RecoverAndNoFlush) {
|
||||
// Set up the option that enables user defined timestmp size.
|
||||
std::string ts = Timestamp(1, 0);
|
||||
const size_t kTimestampSize = ts.size();
|
||||
std::string ts1 = Timestamp(1, 0);
|
||||
const size_t kTimestampSize = ts1.size();
|
||||
TestComparator test_cmp(kTimestampSize);
|
||||
Options ts_options;
|
||||
ts_options.create_if_missing = true;
|
||||
ts_options.comparator = &test_cmp;
|
||||
// Test that user-defined timestamps are recovered from WAL regardless of
|
||||
// the value of this flag because UDTs are saved in WAL nonetheless.
|
||||
// We however need to explicitly disable flush during recovery by setting
|
||||
// `avoid_flush_during_recovery=true` so that we can avoid timestamps getting
|
||||
// stripped when the `persist_user_defined_timestamps` flag is false, so that
|
||||
// all written timestamps are available for testing user-defined time travel
|
||||
// read.
|
||||
ts_options.persist_user_defined_timestamps = persist_udt_;
|
||||
bool avoid_flush_during_recovery = true;
|
||||
|
||||
ReadOptions read_opts;
|
||||
Slice ts_slice = ts;
|
||||
read_opts.timestamp = &ts_slice;
|
||||
do {
|
||||
ASSERT_OK(CreateAndReopenWithCFWithTs({"pikachu"}, ts_options));
|
||||
ASSERT_OK(Put(1, "foo", ts, "v1"));
|
||||
ASSERT_OK(Put(1, "baz", ts, "v5"));
|
||||
Slice ts_slice = ts1;
|
||||
read_opts.timestamp = &ts_slice;
|
||||
ASSERT_OK(CreateAndReopenWithCFWithTs({"pikachu"}, ts_options,
|
||||
avoid_flush_during_recovery));
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "pikachu"), 0U);
|
||||
ASSERT_OK(Put(1, "foo", ts1, "v1"));
|
||||
ASSERT_OK(Put(1, "baz", ts1, "v5"));
|
||||
|
||||
ASSERT_OK(ReopenColumnFamiliesWithTs({"pikachu"}, ts_options));
|
||||
CheckGet(read_opts, 1, "foo", "v1");
|
||||
CheckGet(read_opts, 1, "baz", "v5");
|
||||
ASSERT_OK(Put(1, "bar", ts, "v2"));
|
||||
ASSERT_OK(Put(1, "foo", ts, "v3"));
|
||||
ASSERT_OK(ReopenColumnFamiliesWithTs({"pikachu"}, ts_options,
|
||||
avoid_flush_during_recovery));
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "pikachu"), 0U);
|
||||
// Do a timestamped read with ts1 after second reopen.
|
||||
CheckGet(read_opts, 1, "foo", "v1", ts1);
|
||||
CheckGet(read_opts, 1, "baz", "v5", ts1);
|
||||
|
||||
ASSERT_OK(ReopenColumnFamiliesWithTs({"pikachu"}, ts_options));
|
||||
CheckGet(read_opts, 1, "foo", "v3");
|
||||
ASSERT_OK(Put(1, "foo", ts, "v4"));
|
||||
CheckGet(read_opts, 1, "foo", "v4");
|
||||
CheckGet(read_opts, 1, "bar", "v2");
|
||||
CheckGet(read_opts, 1, "baz", "v5");
|
||||
// Write more value versions for key "foo" and "bar" before and after second
|
||||
// reopen.
|
||||
std::string ts2 = Timestamp(2, 0);
|
||||
ASSERT_OK(Put(1, "bar", ts2, "v2"));
|
||||
ASSERT_OK(Put(1, "foo", ts2, "v3"));
|
||||
|
||||
ASSERT_OK(ReopenColumnFamiliesWithTs({"pikachu"}, ts_options,
|
||||
avoid_flush_during_recovery));
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "pikachu"), 0U);
|
||||
std::string ts3 = Timestamp(3, 0);
|
||||
ASSERT_OK(Put(1, "foo", ts3, "v4"));
|
||||
|
||||
// Do a timestamped read with ts1 after third reopen.
|
||||
CheckGet(read_opts, 1, "foo", "v1", ts1);
|
||||
std::string value;
|
||||
ASSERT_TRUE(db_->Get(read_opts, handles_[1], "bar", &value).IsNotFound());
|
||||
CheckGet(read_opts, 1, "baz", "v5", ts1);
|
||||
|
||||
// Do a timestamped read with ts2 after third reopen.
|
||||
ts_slice = ts2;
|
||||
CheckGet(read_opts, 1, "foo", "v3", ts2);
|
||||
CheckGet(read_opts, 1, "bar", "v2", ts2);
|
||||
CheckGet(read_opts, 1, "baz", "v5", ts1);
|
||||
|
||||
// Do a timestamped read with ts3 after third reopen.
|
||||
ts_slice = ts3;
|
||||
CheckGet(read_opts, 1, "foo", "v4", ts3);
|
||||
CheckGet(read_opts, 1, "bar", "v2", ts2);
|
||||
CheckGet(read_opts, 1, "baz", "v5", ts1);
|
||||
} while (ChangeWalOptions());
|
||||
}
|
||||
|
||||
TEST_F(DBWALTestWithTimestamp, RecoverInconsistentTimestamp) {
|
||||
TEST_P(DBWALTestWithTimestamp, RecoverInconsistentTimestamp) {
|
||||
// Set up the option that enables user defined timestmp size.
|
||||
std::string ts = Timestamp(1, 0);
|
||||
const size_t kTimestampSize = ts.size();
|
||||
@@ -390,17 +443,72 @@ TEST_F(DBWALTestWithTimestamp, RecoverInconsistentTimestamp) {
|
||||
Options ts_options;
|
||||
ts_options.create_if_missing = true;
|
||||
ts_options.comparator = &test_cmp;
|
||||
ts_options.persist_user_defined_timestamps = persist_udt_;
|
||||
|
||||
ASSERT_OK(CreateAndReopenWithCFWithTs({"pikachu"}, ts_options));
|
||||
ASSERT_OK(Put(1, "foo", ts, "v1"));
|
||||
ASSERT_OK(Put(1, "baz", ts, "v5"));
|
||||
|
||||
// In real use cases, switching to a different user comparator is prohibited
|
||||
// by a sanity check during DB open that does a user comparator name
|
||||
// comparison. This test mocked and bypassed that sanity check because the
|
||||
// before and after user comparator are both named "TestComparator". This is
|
||||
// to test the user-defined timestamp recovery logic for WAL files have
|
||||
// the intended consistency check.
|
||||
// `HandleWriteBatchTimestampSizeDifference` in udt_util.h has more details.
|
||||
TestComparator diff_test_cmp(kTimestampSize + 1);
|
||||
ts_options.comparator = &diff_test_cmp;
|
||||
ASSERT_TRUE(
|
||||
ReopenColumnFamiliesWithTs({"pikachu"}, ts_options).IsInvalidArgument());
|
||||
}
|
||||
|
||||
TEST_P(DBWALTestWithTimestamp, RecoverAndFlush) {
|
||||
// Set up the option that enables user defined timestamp size.
|
||||
std::string min_ts = Timestamp(0, 0);
|
||||
std::string write_ts = Timestamp(1, 0);
|
||||
const size_t kTimestampSize = write_ts.size();
|
||||
TestComparator test_cmp(kTimestampSize);
|
||||
Options ts_options;
|
||||
ts_options.create_if_missing = true;
|
||||
ts_options.comparator = &test_cmp;
|
||||
ts_options.persist_user_defined_timestamps = persist_udt_;
|
||||
|
||||
std::string smallest_ukey_without_ts = "baz";
|
||||
std::string largest_ukey_without_ts = "foo";
|
||||
|
||||
ASSERT_OK(CreateAndReopenWithCFWithTs({"pikachu"}, ts_options));
|
||||
// No flush, no sst files, because of no data.
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "pikachu"), 0U);
|
||||
ASSERT_OK(Put(1, largest_ukey_without_ts, write_ts, "v1"));
|
||||
ASSERT_OK(Put(1, smallest_ukey_without_ts, write_ts, "v5"));
|
||||
|
||||
ASSERT_OK(ReopenColumnFamiliesWithTs({"pikachu"}, ts_options));
|
||||
// Memtable recovered from WAL flushed because `avoid_flush_during_recovery`
|
||||
// defaults to false, created one L0 file.
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "pikachu"), 1U);
|
||||
|
||||
std::vector<std::vector<FileMetaData>> level_to_files;
|
||||
dbfull()->TEST_GetFilesMetaData(handles_[1], &level_to_files);
|
||||
ASSERT_GT(level_to_files.size(), 1);
|
||||
// L0 only has one SST file.
|
||||
ASSERT_EQ(level_to_files[0].size(), 1);
|
||||
auto meta = level_to_files[0][0];
|
||||
if (persist_udt_) {
|
||||
ASSERT_EQ(smallest_ukey_without_ts + write_ts, meta.smallest.user_key());
|
||||
ASSERT_EQ(largest_ukey_without_ts + write_ts, meta.largest.user_key());
|
||||
} else {
|
||||
ASSERT_EQ(smallest_ukey_without_ts + min_ts, meta.smallest.user_key());
|
||||
ASSERT_EQ(largest_ukey_without_ts + min_ts, meta.largest.user_key());
|
||||
}
|
||||
}
|
||||
|
||||
// Param 0: test mode for the user-defined timestamp feature
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
DBWALTestWithTimestamp, DBWALTestWithTimestamp,
|
||||
::testing::Values(
|
||||
test::UserDefinedTimestampTestMode::kStripUserDefinedTimestamp,
|
||||
test::UserDefinedTimestampTestMode::kNormal));
|
||||
|
||||
TEST_F(DBWALTest, RecoverWithTableHandle) {
|
||||
do {
|
||||
Options options = CurrentOptions();
|
||||
|
||||
@@ -3273,6 +3273,76 @@ TEST_F(UpdateFullHistoryTsLowTest, ConcurrentUpdate) {
|
||||
Close();
|
||||
}
|
||||
|
||||
// Tests the effect of flag `persist_user_defined_timestamps` on the file
|
||||
// boundaries contained in the Manifest, a.k.a FileMetaData.smallest,
|
||||
// FileMetaData.largest.
|
||||
class HandleFileBoundariesTest
|
||||
: public DBBasicTestWithTimestampBase,
|
||||
public testing::WithParamInterface<test::UserDefinedTimestampTestMode> {
|
||||
public:
|
||||
HandleFileBoundariesTest()
|
||||
: DBBasicTestWithTimestampBase("/handle_file_boundaries") {}
|
||||
};
|
||||
|
||||
TEST_P(HandleFileBoundariesTest, ConfigurePersistUdt) {
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
// Write a timestamp that is not the min timestamp to help test the behavior
|
||||
// of flag `persist_user_defined_timestamps`.
|
||||
std::string write_ts = Timestamp(1, 0);
|
||||
std::string min_ts = Timestamp(0, 0);
|
||||
std::string smallest_ukey_without_ts = "bar";
|
||||
std::string largest_ukey_without_ts = "foo";
|
||||
const size_t kTimestampSize = write_ts.size();
|
||||
TestComparator test_cmp(kTimestampSize);
|
||||
options.comparator = &test_cmp;
|
||||
bool persist_udt = test::ShouldPersistUDT(GetParam());
|
||||
options.persist_user_defined_timestamps = persist_udt;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
ASSERT_OK(
|
||||
db_->Put(WriteOptions(), smallest_ukey_without_ts, write_ts, "val1"));
|
||||
ASSERT_OK(
|
||||
db_->Put(WriteOptions(), largest_ukey_without_ts, write_ts, "val2"));
|
||||
|
||||
// Create a L0 SST file and its record is added to the Manfiest.
|
||||
ASSERT_OK(Flush());
|
||||
Close();
|
||||
|
||||
options.create_if_missing = false;
|
||||
// Reopen the DB and process manifest file.
|
||||
Reopen(options);
|
||||
|
||||
std::vector<std::vector<FileMetaData>> level_to_files;
|
||||
dbfull()->TEST_GetFilesMetaData(dbfull()->DefaultColumnFamily(),
|
||||
&level_to_files);
|
||||
ASSERT_GT(level_to_files.size(), 1);
|
||||
// L0 only has one SST file.
|
||||
ASSERT_EQ(level_to_files[0].size(), 1);
|
||||
auto file_meta = level_to_files[0][0];
|
||||
if (persist_udt) {
|
||||
ASSERT_EQ(smallest_ukey_without_ts + write_ts,
|
||||
file_meta.smallest.user_key());
|
||||
ASSERT_EQ(largest_ukey_without_ts + write_ts, file_meta.largest.user_key());
|
||||
} else {
|
||||
// If `persist_user_defined_timestamps` is false, the file boundaries should
|
||||
// have the min timestamp. Behind the scenes, when file boundaries in
|
||||
// FileMetaData is persisted to Manifest, the original user-defined
|
||||
// timestamps in user key are stripped. When manifest is read and processed
|
||||
// during DB open, a min timestamp is padded to the file boundaries. This
|
||||
// test's writes contain non min timestamp to verify this logic end-to-end.
|
||||
ASSERT_EQ(smallest_ukey_without_ts + min_ts, file_meta.smallest.user_key());
|
||||
ASSERT_EQ(largest_ukey_without_ts + min_ts, file_meta.largest.user_key());
|
||||
}
|
||||
Close();
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
ConfigurePersistUdt, HandleFileBoundariesTest,
|
||||
::testing::Values(
|
||||
test::UserDefinedTimestampTestMode::kStripUserDefinedTimestamp,
|
||||
test::UserDefinedTimestampTestMode::kNormal));
|
||||
|
||||
TEST_F(DBBasicTestWithTimestamp,
|
||||
GCPreserveRangeTombstoneWhenNoOrSmallFullHistoryLow) {
|
||||
Options options = CurrentOptions();
|
||||
|
||||
@@ -120,6 +120,16 @@ void StripTimestampFromInternalKey(std::string* result, const Slice& key,
|
||||
kNumInternalBytes);
|
||||
}
|
||||
|
||||
void ReplaceInternalKeyWithMinTimestamp(std::string* result, const Slice& key,
|
||||
size_t ts_sz) {
|
||||
const size_t key_sz = key.size();
|
||||
assert(key_sz >= ts_sz + kNumInternalBytes);
|
||||
result->reserve(key_sz);
|
||||
result->append(key.data(), key_sz - kNumInternalBytes - ts_sz);
|
||||
result->append(ts_sz, static_cast<unsigned char>(0));
|
||||
result->append(key.data() + key_sz - kNumInternalBytes, kNumInternalBytes);
|
||||
}
|
||||
|
||||
std::string ParsedInternalKey::DebugString(bool log_err_key, bool hex) const {
|
||||
std::string result = "'";
|
||||
if (log_err_key) {
|
||||
|
||||
+28
-23
@@ -168,51 +168,57 @@ inline void UnPackSequenceAndType(uint64_t packed, uint64_t* seq,
|
||||
EntryType GetEntryType(ValueType value_type);
|
||||
|
||||
// Append the serialization of "key" to *result.
|
||||
extern void AppendInternalKey(std::string* result,
|
||||
const ParsedInternalKey& key);
|
||||
void AppendInternalKey(std::string* result, const ParsedInternalKey& key);
|
||||
|
||||
// Append the serialization of "key" to *result, replacing the original
|
||||
// timestamp with argument ts.
|
||||
extern void AppendInternalKeyWithDifferentTimestamp(
|
||||
std::string* result, const ParsedInternalKey& key, const Slice& ts);
|
||||
void AppendInternalKeyWithDifferentTimestamp(std::string* result,
|
||||
const ParsedInternalKey& key,
|
||||
const Slice& ts);
|
||||
|
||||
// Serialized internal key consists of user key followed by footer.
|
||||
// This function appends the footer to *result, assuming that *result already
|
||||
// contains the user key at the end.
|
||||
extern void AppendInternalKeyFooter(std::string* result, SequenceNumber s,
|
||||
ValueType t);
|
||||
void AppendInternalKeyFooter(std::string* result, SequenceNumber s,
|
||||
ValueType t);
|
||||
|
||||
// Append the key and a minimal timestamp to *result
|
||||
extern void AppendKeyWithMinTimestamp(std::string* result, const Slice& key,
|
||||
size_t ts_sz);
|
||||
void AppendKeyWithMinTimestamp(std::string* result, const Slice& key,
|
||||
size_t ts_sz);
|
||||
|
||||
// Append the key and a maximal timestamp to *result
|
||||
extern void AppendKeyWithMaxTimestamp(std::string* result, const Slice& key,
|
||||
size_t ts_sz);
|
||||
void AppendKeyWithMaxTimestamp(std::string* result, const Slice& key,
|
||||
size_t ts_sz);
|
||||
|
||||
// `key` is a user key with timestamp. Append the user key without timestamp
|
||||
// and the maximal timestamp to *result.
|
||||
extern void AppendUserKeyWithMaxTimestamp(std::string* result, const Slice& key,
|
||||
size_t ts_sz);
|
||||
void AppendUserKeyWithMaxTimestamp(std::string* result, const Slice& key,
|
||||
size_t ts_sz);
|
||||
|
||||
// `key` is an internal key containing a user key without timestamp. Create a
|
||||
// new key in *result by padding a min timestamp of size `ts_sz` to the user key
|
||||
// and copying the remaining internal key bytes.
|
||||
extern void PadInternalKeyWithMinTimestamp(std::string* result,
|
||||
const Slice& key, size_t ts_sz);
|
||||
void PadInternalKeyWithMinTimestamp(std::string* result, const Slice& key,
|
||||
size_t ts_sz);
|
||||
|
||||
// `key` is an internal key containing a user key with timestamp of size
|
||||
// `ts_sz`. Create a new internal key in *result by stripping the timestamp from
|
||||
// the user key and copying the remaining internal key bytes.
|
||||
extern void StripTimestampFromInternalKey(std::string* result, const Slice& key,
|
||||
size_t ts_sz);
|
||||
void StripTimestampFromInternalKey(std::string* result, const Slice& key,
|
||||
size_t ts_sz);
|
||||
|
||||
// `key` is an internal key containing a user key with timestamp of size
|
||||
// `ts_sz`. Create a new internal key in *result while replace the original
|
||||
// timestamp with min timestamp.
|
||||
void ReplaceInternalKeyWithMinTimestamp(std::string* result, const Slice& key,
|
||||
size_t ts_sz);
|
||||
|
||||
// Attempt to parse an internal key from "internal_key". On success,
|
||||
// stores the parsed data in "*result", and returns true.
|
||||
//
|
||||
// On error, returns false, leaves "*result" in an undefined state.
|
||||
extern Status ParseInternalKey(const Slice& internal_key,
|
||||
ParsedInternalKey* result, bool log_err_key);
|
||||
Status ParseInternalKey(const Slice& internal_key, ParsedInternalKey* result,
|
||||
bool log_err_key);
|
||||
|
||||
// Returns the user key portion of an internal key.
|
||||
inline Slice ExtractUserKey(const Slice& internal_key) {
|
||||
@@ -783,8 +789,7 @@ class InternalKeySliceTransform : public SliceTransform {
|
||||
// Read the key of a record from a write batch.
|
||||
// if this record represent the default column family then cf_record
|
||||
// must be passed as false, otherwise it must be passed as true.
|
||||
extern bool ReadKeyFromWriteBatchEntry(Slice* input, Slice* key,
|
||||
bool cf_record);
|
||||
bool ReadKeyFromWriteBatchEntry(Slice* input, Slice* key, bool cf_record);
|
||||
|
||||
// Read record from a write batch piece from input.
|
||||
// tag, column_family, key, value and blob are return values. Callers own the
|
||||
@@ -793,9 +798,9 @@ extern bool ReadKeyFromWriteBatchEntry(Slice* input, Slice* key,
|
||||
// input will be advanced to after the record.
|
||||
// If user-defined timestamp is enabled for a column family, then the `key`
|
||||
// resulting from this call will include timestamp.
|
||||
extern Status ReadRecordFromWriteBatch(Slice* input, char* tag,
|
||||
uint32_t* column_family, Slice* key,
|
||||
Slice* value, Slice* blob, Slice* xid);
|
||||
Status ReadRecordFromWriteBatch(Slice* input, char* tag,
|
||||
uint32_t* column_family, Slice* key,
|
||||
Slice* value, Slice* blob, Slice* xid);
|
||||
|
||||
// When user call DeleteRange() to delete a range of keys,
|
||||
// we will store a serialized RangeTombstone in MemTable and SST.
|
||||
|
||||
@@ -312,6 +312,27 @@ TEST_F(FormatTest, StripTimestampFromInternalKey) {
|
||||
ASSERT_EQ(kTypeValue, key_without_timestamp.type);
|
||||
}
|
||||
|
||||
TEST_F(FormatTest, ReplaceInternalKeyWithMinTimestamp) {
|
||||
std::string orig_user_key = "foo";
|
||||
size_t ts_sz = 8;
|
||||
orig_user_key.append(ts_sz, static_cast<unsigned char>(1));
|
||||
std::string orig_internal_key = IKey(orig_user_key, 100, kTypeValue);
|
||||
|
||||
std::string key_buf;
|
||||
ReplaceInternalKeyWithMinTimestamp(&key_buf, orig_internal_key, ts_sz);
|
||||
ParsedInternalKey new_key;
|
||||
Slice in(key_buf);
|
||||
ASSERT_OK(ParseInternalKey(in, &new_key, true /*log_err_key*/));
|
||||
|
||||
std::string min_timestamp(ts_sz, static_cast<unsigned char>(0));
|
||||
size_t ukey_diff_offset = new_key.user_key.difference_offset(orig_user_key);
|
||||
ASSERT_EQ(min_timestamp,
|
||||
Slice(new_key.user_key.data() + ukey_diff_offset, ts_sz));
|
||||
ASSERT_EQ(orig_user_key.size(), new_key.user_key.size());
|
||||
ASSERT_EQ(100, new_key.sequence);
|
||||
ASSERT_EQ(kTypeValue, new_key.type);
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
+2
-4
@@ -56,17 +56,15 @@ void FileIndexer::GetNextLevelIndex(const size_t level, const size_t file_index,
|
||||
} else if (cmp_smallest == 0) {
|
||||
*left_bound = index.smallest_lb;
|
||||
*right_bound = index.smallest_rb;
|
||||
} else if (cmp_smallest > 0 && cmp_largest < 0) {
|
||||
} else if (cmp_largest < 0) {
|
||||
*left_bound = index.smallest_lb;
|
||||
*right_bound = index.largest_rb;
|
||||
} else if (cmp_largest == 0) {
|
||||
*left_bound = index.largest_lb;
|
||||
*right_bound = index.largest_rb;
|
||||
} else if (cmp_largest > 0) {
|
||||
} else {
|
||||
*left_bound = index.largest_lb;
|
||||
*right_bound = level_rb_[level + 1];
|
||||
} else {
|
||||
assert(false);
|
||||
}
|
||||
|
||||
assert(*left_bound >= 0);
|
||||
|
||||
+72
-11
@@ -117,6 +117,8 @@ class FlushJobTestBase : public testing::Test {
|
||||
db_options_.statistics = CreateDBStatistics();
|
||||
|
||||
cf_options_.comparator = ucmp_;
|
||||
cf_options_.persist_user_defined_timestamps = persist_udt_;
|
||||
cf_options_.paranoid_file_checks = paranoid_file_checks_;
|
||||
|
||||
std::vector<ColumnFamilyDescriptor> column_families;
|
||||
cf_options_.table_factory = mock_table_factory_;
|
||||
@@ -149,6 +151,9 @@ class FlushJobTestBase : public testing::Test {
|
||||
std::atomic<bool> shutting_down_;
|
||||
std::shared_ptr<mock::MockTableFactory> mock_table_factory_;
|
||||
|
||||
bool persist_udt_ = true;
|
||||
bool paranoid_file_checks_ = false;
|
||||
|
||||
SeqnoToTimeMapping empty_seqno_to_time_mapping_;
|
||||
};
|
||||
|
||||
@@ -600,7 +605,13 @@ TEST_F(FlushJobTest, GetRateLimiterPriorityForWrite) {
|
||||
}
|
||||
}
|
||||
|
||||
class FlushJobTimestampTest : public FlushJobTestBase {
|
||||
// Test parameters:
|
||||
// param 0): paranoid file check
|
||||
// param 1): user-defined timestamp test mode
|
||||
class FlushJobTimestampTest
|
||||
: public FlushJobTestBase,
|
||||
public testing::WithParamInterface<
|
||||
std::tuple<bool, test::UserDefinedTimestampTestMode>> {
|
||||
public:
|
||||
FlushJobTimestampTest()
|
||||
: FlushJobTestBase(test::PerThreadDBPath("flush_job_ts_gc_test"),
|
||||
@@ -616,13 +627,36 @@ class FlushJobTimestampTest : public FlushJobTestBase {
|
||||
}
|
||||
|
||||
protected:
|
||||
void SetUp() override {
|
||||
paranoid_file_checks_ = std::get<0>(GetParam());
|
||||
auto udt_test_mode = std::get<1>(GetParam());
|
||||
persist_udt_ = test::ShouldPersistUDT(udt_test_mode);
|
||||
FlushJobTestBase::SetUp();
|
||||
}
|
||||
static constexpr uint64_t kStartTs = 10;
|
||||
static constexpr SequenceNumber kStartSeq = 0;
|
||||
SequenceNumber curr_seq_{kStartSeq};
|
||||
std::atomic<uint64_t> curr_ts_{kStartTs};
|
||||
|
||||
void CheckFileMetaData(ColumnFamilyData* cfd,
|
||||
const InternalKey& expected_smallest,
|
||||
const InternalKey& expected_largest,
|
||||
const FileMetaData* meta_from_flush) const {
|
||||
ASSERT_EQ(expected_smallest.Encode(), meta_from_flush->smallest.Encode());
|
||||
ASSERT_EQ(expected_largest.Encode(), meta_from_flush->largest.Encode());
|
||||
|
||||
const VersionStorageInfo* storage_info = cfd->current()->storage_info();
|
||||
const std::vector<FileMetaData*>& l0_files = storage_info->LevelFiles(0);
|
||||
|
||||
ASSERT_EQ(l0_files.size(), 1);
|
||||
auto installed_file_meta = l0_files[0];
|
||||
ASSERT_EQ(expected_smallest.Encode(),
|
||||
installed_file_meta->smallest.Encode());
|
||||
ASSERT_EQ(expected_largest.Encode(), installed_file_meta->largest.Encode());
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(FlushJobTimestampTest, AllKeysExpired) {
|
||||
TEST_P(FlushJobTimestampTest, AllKeysExpired) {
|
||||
ColumnFamilyData* cfd = versions_->GetColumnFamilySet()->GetDefault();
|
||||
autovector<MemTable*> to_delete;
|
||||
|
||||
@@ -669,17 +703,24 @@ TEST_F(FlushJobTimestampTest, AllKeysExpired) {
|
||||
|
||||
{
|
||||
std::string key = test::EncodeInt(0);
|
||||
key.append(test::EncodeInt(curr_ts_.load(std::memory_order_relaxed) - 1));
|
||||
if (!persist_udt_) {
|
||||
// When `AdvancedColumnFamilyOptions.persist_user_defined_timestamps` flag
|
||||
// is set to false. The user-defined timestamp is stripped from user key
|
||||
// during flush, making the user key logically containing the minimum
|
||||
// timestamp.
|
||||
key.append(test::EncodeInt(0));
|
||||
} else {
|
||||
key.append(test::EncodeInt(curr_ts_.load(std::memory_order_relaxed) - 1));
|
||||
}
|
||||
InternalKey ikey(key, curr_seq_ - 1, ValueType::kTypeDeletionWithTimestamp);
|
||||
ASSERT_EQ(ikey.Encode(), fmeta.smallest.Encode());
|
||||
ASSERT_EQ(ikey.Encode(), fmeta.largest.Encode());
|
||||
CheckFileMetaData(cfd, ikey, ikey, &fmeta);
|
||||
}
|
||||
|
||||
job_context.Clean();
|
||||
ASSERT_TRUE(to_delete.empty());
|
||||
}
|
||||
|
||||
TEST_F(FlushJobTimestampTest, NoKeyExpired) {
|
||||
TEST_P(FlushJobTimestampTest, NoKeyExpired) {
|
||||
ColumnFamilyData* cfd = versions_->GetColumnFamilySet()->GetDefault();
|
||||
autovector<MemTable*> to_delete;
|
||||
|
||||
@@ -722,18 +763,38 @@ TEST_F(FlushJobTimestampTest, NoKeyExpired) {
|
||||
|
||||
{
|
||||
std::string ukey = test::EncodeInt(0);
|
||||
std::string smallest_key =
|
||||
ukey + test::EncodeInt(curr_ts_.load(std::memory_order_relaxed) - 1);
|
||||
std::string largest_key = ukey + test::EncodeInt(kStartTs);
|
||||
std::string smallest_key;
|
||||
std::string largest_key;
|
||||
if (!persist_udt_) {
|
||||
// When `AdvancedColumnFamilyOptions.persist_user_defined_timestamps` flag
|
||||
// is set to false. The user-defined timestamp is stripped from user key
|
||||
// during flush, making the user key logically containing the minimum
|
||||
// timestamp, which is hardcoded to be all zeros for now.
|
||||
smallest_key = ukey + test::EncodeInt(0);
|
||||
largest_key = ukey + test::EncodeInt(0);
|
||||
} else {
|
||||
smallest_key =
|
||||
ukey + test::EncodeInt(curr_ts_.load(std::memory_order_relaxed) - 1);
|
||||
largest_key = ukey + test::EncodeInt(kStartTs);
|
||||
}
|
||||
InternalKey smallest(smallest_key, curr_seq_ - 1, ValueType::kTypeValue);
|
||||
InternalKey largest(largest_key, kStartSeq, ValueType::kTypeValue);
|
||||
ASSERT_EQ(smallest.Encode(), fmeta.smallest.Encode());
|
||||
ASSERT_EQ(largest.Encode(), fmeta.largest.Encode());
|
||||
CheckFileMetaData(cfd, smallest, largest, &fmeta);
|
||||
}
|
||||
job_context.Clean();
|
||||
ASSERT_TRUE(to_delete.empty());
|
||||
}
|
||||
|
||||
// Param 0: paranoid file check
|
||||
// Param 1: test mode for the user-defined timestamp feature
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
FlushJobTimestampTest, FlushJobTimestampTest,
|
||||
::testing::Combine(
|
||||
::testing::Bool(),
|
||||
::testing::Values(
|
||||
test::UserDefinedTimestampTestMode::kStripUserDefinedTimestamp,
|
||||
test::UserDefinedTimestampTestMode::kNormal)));
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
+1
-1
@@ -810,7 +810,7 @@ Status MemTable::Add(SequenceNumber s, ValueType type,
|
||||
earliest_seqno_.load(std::memory_order_relaxed);
|
||||
while (
|
||||
(cur_earliest_seqno == kMaxSequenceNumber || s < cur_earliest_seqno) &&
|
||||
!first_seqno_.compare_exchange_weak(cur_earliest_seqno, s)) {
|
||||
!earliest_seqno_.compare_exchange_weak(cur_earliest_seqno, s)) {
|
||||
}
|
||||
}
|
||||
if (type == kTypeRangeDeletion) {
|
||||
|
||||
+75
-9
@@ -315,7 +315,13 @@ TEST_F(RepairTest, UnflushedSst) {
|
||||
ASSERT_EQ(Get("key"), "val");
|
||||
}
|
||||
|
||||
class RepairTestWithTimestamp : public DBBasicTestWithTimestampBase {
|
||||
// Test parameters:
|
||||
// param 0): paranoid file check
|
||||
// param 1): user-defined timestamp test mode
|
||||
class RepairTestWithTimestamp
|
||||
: public DBBasicTestWithTimestampBase,
|
||||
public testing::WithParamInterface<
|
||||
std::tuple<bool, test::UserDefinedTimestampTestMode>> {
|
||||
public:
|
||||
RepairTestWithTimestamp()
|
||||
: DBBasicTestWithTimestampBase("repair_test_with_timestamp") {}
|
||||
@@ -326,23 +332,46 @@ class RepairTestWithTimestamp : public DBBasicTestWithTimestampBase {
|
||||
}
|
||||
|
||||
void CheckGet(const ReadOptions& read_opts, const Slice& key,
|
||||
const std::string& expected_value) {
|
||||
const std::string& expected_value,
|
||||
const std::string& expected_ts) {
|
||||
std::string actual_value;
|
||||
ASSERT_OK(db_->Get(read_opts, handles_[0], key, &actual_value));
|
||||
std::string actual_ts;
|
||||
ASSERT_OK(db_->Get(read_opts, handles_[0], key, &actual_value, &actual_ts));
|
||||
ASSERT_EQ(expected_value, actual_value);
|
||||
ASSERT_EQ(expected_ts, actual_ts);
|
||||
}
|
||||
|
||||
void CheckFileBoundaries(const Slice& smallest_user_key,
|
||||
const Slice& largest_user_key) {
|
||||
std::vector<std::vector<FileMetaData>> level_to_files;
|
||||
dbfull()->TEST_GetFilesMetaData(dbfull()->DefaultColumnFamily(),
|
||||
&level_to_files);
|
||||
ASSERT_GT(level_to_files.size(), 1);
|
||||
// L0 only has one SST file.
|
||||
ASSERT_EQ(level_to_files[0].size(), 1);
|
||||
auto file_meta = level_to_files[0][0];
|
||||
ASSERT_EQ(smallest_user_key, file_meta.smallest.user_key());
|
||||
ASSERT_EQ(largest_user_key, file_meta.largest.user_key());
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(RepairTestWithTimestamp, UnflushedSst) {
|
||||
TEST_P(RepairTestWithTimestamp, UnflushedSst) {
|
||||
Destroy(last_options_);
|
||||
|
||||
bool paranoid_file_checks = std::get<0>(GetParam());
|
||||
bool persist_udt = test::ShouldPersistUDT(std::get<1>(GetParam()));
|
||||
std::string smallest_ukey_without_ts = "bar";
|
||||
std::string largest_ukey_without_ts = "foo";
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.create_if_missing = true;
|
||||
std::string ts = Timestamp(0, 0);
|
||||
const size_t kTimestampSize = ts.size();
|
||||
std::string min_ts = Timestamp(0, 0);
|
||||
std::string write_ts = Timestamp(1, 0);
|
||||
const size_t kTimestampSize = write_ts.size();
|
||||
TestComparator test_cmp(kTimestampSize);
|
||||
options.comparator = &test_cmp;
|
||||
options.persist_user_defined_timestamps = persist_udt;
|
||||
options.paranoid_file_checks = paranoid_file_checks;
|
||||
|
||||
ColumnFamilyOptions cf_options(options);
|
||||
std::vector<ColumnFamilyDescriptor> column_families;
|
||||
@@ -351,7 +380,10 @@ TEST_F(RepairTestWithTimestamp, UnflushedSst) {
|
||||
|
||||
ASSERT_OK(DB::Open(options, dbname_, column_families, &handles_, &db_));
|
||||
|
||||
ASSERT_OK(Put("key", ts, "val"));
|
||||
ASSERT_OK(Put(smallest_ukey_without_ts, write_ts,
|
||||
smallest_ukey_without_ts + ":val"));
|
||||
ASSERT_OK(
|
||||
Put(largest_ukey_without_ts, write_ts, largest_ukey_without_ts + ":val"));
|
||||
VectorLogPtr wal_files;
|
||||
ASSERT_OK(dbfull()->GetSortedWalFiles(wal_files));
|
||||
ASSERT_EQ(wal_files.size(), 1);
|
||||
@@ -381,12 +413,46 @@ TEST_F(RepairTestWithTimestamp, UnflushedSst) {
|
||||
ASSERT_GT(total_ssts_size, 0);
|
||||
}
|
||||
|
||||
// Check file boundaries are correct for different
|
||||
// `persist_user_defined_timestamps` option values.
|
||||
if (persist_udt) {
|
||||
CheckFileBoundaries(smallest_ukey_without_ts + write_ts,
|
||||
largest_ukey_without_ts + write_ts);
|
||||
} else {
|
||||
CheckFileBoundaries(smallest_ukey_without_ts + min_ts,
|
||||
largest_ukey_without_ts + min_ts);
|
||||
}
|
||||
|
||||
ReadOptions read_opts;
|
||||
Slice read_ts_slice = ts;
|
||||
Slice read_ts_slice = write_ts;
|
||||
read_opts.timestamp = &read_ts_slice;
|
||||
CheckGet(read_opts, "key", "val");
|
||||
if (persist_udt) {
|
||||
CheckGet(read_opts, smallest_ukey_without_ts,
|
||||
smallest_ukey_without_ts + ":val", write_ts);
|
||||
CheckGet(read_opts, largest_ukey_without_ts,
|
||||
largest_ukey_without_ts + ":val", write_ts);
|
||||
} else {
|
||||
// TODO(yuzhangyu): currently when `persist_user_defined_timestamps` is
|
||||
// false, ts is unconditionally stripped during flush.
|
||||
// When `full_history_ts_low` is set and respected during flush.
|
||||
// We should prohibit reading below `full_history_ts_low` all together.
|
||||
CheckGet(read_opts, smallest_ukey_without_ts,
|
||||
smallest_ukey_without_ts + ":val", min_ts);
|
||||
CheckGet(read_opts, largest_ukey_without_ts,
|
||||
largest_ukey_without_ts + ":val", min_ts);
|
||||
}
|
||||
}
|
||||
|
||||
// Param 0: paranoid file check
|
||||
// Param 1: test mode for the user-defined timestamp feature
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
UnflushedSst, RepairTestWithTimestamp,
|
||||
::testing::Combine(
|
||||
::testing::Bool(),
|
||||
::testing::Values(
|
||||
test::UserDefinedTimestampTestMode::kStripUserDefinedTimestamp,
|
||||
test::UserDefinedTimestampTestMode::kNormal)));
|
||||
|
||||
TEST_F(RepairTest, SeparateWalDir) {
|
||||
do {
|
||||
Options options = CurrentOptions();
|
||||
|
||||
+9
-5
@@ -111,13 +111,17 @@ Status TableCache::GetTableReader(
|
||||
RecordTick(ioptions_.stats, NO_FILE_OPENS);
|
||||
} else if (s.IsPathNotFound()) {
|
||||
fname = Rocks2LevelTableFileName(fname);
|
||||
s = PrepareIOFromReadOptions(ro, ioptions_.clock, fopts.io_options);
|
||||
if (s.ok()) {
|
||||
s = ioptions_.fs->NewRandomAccessFile(fname, file_options, &file,
|
||||
nullptr);
|
||||
// If this file is also not found, we want to use the error message
|
||||
// that contains the table file name which is less confusing.
|
||||
Status temp_s =
|
||||
PrepareIOFromReadOptions(ro, ioptions_.clock, fopts.io_options);
|
||||
if (temp_s.ok()) {
|
||||
temp_s = ioptions_.fs->NewRandomAccessFile(fname, file_options, &file,
|
||||
nullptr);
|
||||
}
|
||||
if (s.ok()) {
|
||||
if (temp_s.ok()) {
|
||||
RecordTick(ioptions_.stats, NO_FILE_OPENS);
|
||||
s = temp_s;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+22
-3
@@ -93,7 +93,8 @@ void VersionEdit::Clear() {
|
||||
full_history_ts_low_.clear();
|
||||
}
|
||||
|
||||
bool VersionEdit::EncodeTo(std::string* dst) const {
|
||||
bool VersionEdit::EncodeTo(std::string* dst,
|
||||
std::optional<size_t> ts_sz) const {
|
||||
if (has_db_id_) {
|
||||
PutVarint32(dst, kDbId);
|
||||
PutLengthPrefixedSlice(dst, db_id_);
|
||||
@@ -133,6 +134,8 @@ bool VersionEdit::EncodeTo(std::string* dst) const {
|
||||
}
|
||||
|
||||
bool min_log_num_written = false;
|
||||
|
||||
assert(new_files_.empty() || ts_sz.has_value());
|
||||
for (size_t i = 0; i < new_files_.size(); i++) {
|
||||
const FileMetaData& f = new_files_[i].second;
|
||||
if (!f.smallest.Valid() || !f.largest.Valid() ||
|
||||
@@ -142,8 +145,7 @@ bool VersionEdit::EncodeTo(std::string* dst) const {
|
||||
PutVarint32(dst, kNewFile4);
|
||||
PutVarint32Varint64(dst, new_files_[i].first /* level */, f.fd.GetNumber());
|
||||
PutVarint64(dst, f.fd.GetFileSize());
|
||||
PutLengthPrefixedSlice(dst, f.smallest.Encode());
|
||||
PutLengthPrefixedSlice(dst, f.largest.Encode());
|
||||
EncodeFileBoundaries(dst, f, ts_sz.value());
|
||||
PutVarint64Varint64(dst, f.fd.smallest_seqno, f.fd.largest_seqno);
|
||||
// Customized fields' format:
|
||||
// +-----------------------------+
|
||||
@@ -458,6 +460,23 @@ const char* VersionEdit::DecodeNewFile4From(Slice* input) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void VersionEdit::EncodeFileBoundaries(std::string* dst,
|
||||
const FileMetaData& meta,
|
||||
size_t ts_sz) const {
|
||||
if (ts_sz == 0 || meta.user_defined_timestamps_persisted) {
|
||||
PutLengthPrefixedSlice(dst, meta.smallest.Encode());
|
||||
PutLengthPrefixedSlice(dst, meta.largest.Encode());
|
||||
return;
|
||||
}
|
||||
std::string smallest_buf;
|
||||
std::string largest_buf;
|
||||
StripTimestampFromInternalKey(&smallest_buf, meta.smallest.Encode(), ts_sz);
|
||||
StripTimestampFromInternalKey(&largest_buf, meta.largest.Encode(), ts_sz);
|
||||
PutLengthPrefixedSlice(dst, smallest_buf);
|
||||
PutLengthPrefixedSlice(dst, largest_buf);
|
||||
return;
|
||||
};
|
||||
|
||||
Status VersionEdit::DecodeFrom(const Slice& src) {
|
||||
Clear();
|
||||
#ifndef NDEBUG
|
||||
|
||||
+20
-1
@@ -9,6 +9,7 @@
|
||||
|
||||
#pragma once
|
||||
#include <algorithm>
|
||||
#include <optional>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
@@ -489,6 +490,8 @@ class VersionEdit {
|
||||
using NewFiles = std::vector<std::pair<int, FileMetaData>>;
|
||||
const NewFiles& GetNewFiles() const { return new_files_; }
|
||||
|
||||
NewFiles& GetMutableNewFiles() { return new_files_; }
|
||||
|
||||
// Retrieve all the compact cursors
|
||||
using CompactCursors = std::vector<std::pair<int, InternalKey>>;
|
||||
const CompactCursors& GetCompactCursors() const { return compact_cursors_; }
|
||||
@@ -639,7 +642,17 @@ class VersionEdit {
|
||||
}
|
||||
|
||||
// return true on success.
|
||||
bool EncodeTo(std::string* dst) const;
|
||||
// `ts_sz` is the size in bytes for the user-defined timestamp contained in
|
||||
// a user key. This argument is optional because it's only required for
|
||||
// encoding a `VersionEdit` with new SST files to add. It's used to handle the
|
||||
// file boundaries: `smallest`, `largest` when
|
||||
// `FileMetaData.user_defined_timestamps_persisted` is false. When reading
|
||||
// the Manifest file, a mirroring change needed to handle
|
||||
// file boundaries are not added to the `VersionEdit.DecodeFrom` function
|
||||
// because timestamp size is not available at `VersionEdit` decoding time,
|
||||
// it's instead added to `VersionEditHandler::OnNonCfOperation`.
|
||||
bool EncodeTo(std::string* dst,
|
||||
std::optional<size_t> ts_sz = std::nullopt) const;
|
||||
Status DecodeFrom(const Slice& src);
|
||||
|
||||
std::string DebugString(bool hex_key = false) const;
|
||||
@@ -660,6 +673,12 @@ class VersionEdit {
|
||||
|
||||
const char* DecodeNewFile4From(Slice* input);
|
||||
|
||||
// Encode file boundaries `FileMetaData.smallest` and `FileMetaData.largest`.
|
||||
// User-defined timestamps in the user key will be stripped if they shouldn't
|
||||
// be persisted.
|
||||
void EncodeFileBoundaries(std::string* dst, const FileMetaData& meta,
|
||||
size_t ts_sz) const;
|
||||
|
||||
int max_level_ = 0;
|
||||
std::string db_id_;
|
||||
std::string comparator_;
|
||||
|
||||
@@ -86,7 +86,8 @@ void VersionEditHandlerBase::Iterate(log::Reader& reader,
|
||||
message << ' ';
|
||||
}
|
||||
// append the filename to the corruption message
|
||||
message << "in file " << reader.file()->file_name();
|
||||
message << " The file " << reader.file()->file_name()
|
||||
<< " may be corrupted.";
|
||||
// overwrite the status with the extended status
|
||||
s = Status(s.code(), s.subcode(), s.severity(), message.str());
|
||||
}
|
||||
@@ -308,6 +309,17 @@ Status VersionEditHandler::OnNonCfOperation(VersionEdit& edit,
|
||||
tmp_cfd = version_set_->GetColumnFamilySet()->GetColumnFamily(
|
||||
edit.column_family_);
|
||||
assert(tmp_cfd != nullptr);
|
||||
// It's important to handle file boundaries before `MaybeCreateVersion`
|
||||
// because `VersionEditHandlerPointInTime::MaybeCreateVersion` does
|
||||
// `FileMetaData` verification that involves the file boundaries.
|
||||
// All `VersionEditHandlerBase` subclasses that need to deal with
|
||||
// `FileMetaData` for new files are also subclasses of
|
||||
// `VersionEditHandler`, so it's sufficient to do the file boundaries
|
||||
// handling in this method.
|
||||
s = MaybeHandleFileBoundariesForNewFiles(edit, tmp_cfd);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
s = MaybeCreateVersion(edit, tmp_cfd, /*force_create_version=*/false);
|
||||
if (s.ok()) {
|
||||
s = builder_iter->second->version_builder()->Apply(&edit);
|
||||
@@ -647,6 +659,47 @@ Status VersionEditHandler::ExtractInfoFromVersionEdit(ColumnFamilyData* cfd,
|
||||
return s;
|
||||
}
|
||||
|
||||
Status VersionEditHandler::MaybeHandleFileBoundariesForNewFiles(
|
||||
VersionEdit& edit, const ColumnFamilyData* cfd) {
|
||||
if (edit.GetNewFiles().empty()) {
|
||||
return Status::OK();
|
||||
}
|
||||
auto ucmp = cfd->user_comparator();
|
||||
assert(ucmp);
|
||||
size_t ts_sz = ucmp->timestamp_size();
|
||||
if (ts_sz == 0) {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
VersionEdit::NewFiles& new_files = edit.GetMutableNewFiles();
|
||||
assert(!new_files.empty());
|
||||
bool file_boundaries_need_handling = false;
|
||||
for (auto& new_file : new_files) {
|
||||
FileMetaData& meta = new_file.second;
|
||||
if (meta.user_defined_timestamps_persisted) {
|
||||
// `FileMetaData.user_defined_timestamps_persisted` field is the value of
|
||||
// the flag `AdvancedColumnFamilyOptions.persist_user_defined_timestamps`
|
||||
// at the time when the SST file was created. As a result, all added SST
|
||||
// files in one `VersionEdit` should have the same value for it.
|
||||
if (file_boundaries_need_handling) {
|
||||
return Status::Corruption(
|
||||
"New files in one VersionEdit has different "
|
||||
"user_defined_timestamps_persisted value.");
|
||||
}
|
||||
break;
|
||||
}
|
||||
file_boundaries_need_handling = true;
|
||||
std::string smallest_buf;
|
||||
std::string largest_buf;
|
||||
PadInternalKeyWithMinTimestamp(&smallest_buf, meta.smallest.Encode(),
|
||||
ts_sz);
|
||||
PadInternalKeyWithMinTimestamp(&largest_buf, meta.largest.Encode(), ts_sz);
|
||||
meta.smallest.DecodeFrom(smallest_buf);
|
||||
meta.largest.DecodeFrom(largest_buf);
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
VersionEditHandlerPointInTime::VersionEditHandlerPointInTime(
|
||||
bool read_only, std::vector<ColumnFamilyDescriptor> column_families,
|
||||
VersionSet* version_set, const std::shared_ptr<IOTracer>& io_tracer,
|
||||
|
||||
@@ -206,6 +206,17 @@ class VersionEditHandler : public VersionEditHandlerBase {
|
||||
private:
|
||||
Status ExtractInfoFromVersionEdit(ColumnFamilyData* cfd,
|
||||
const VersionEdit& edit);
|
||||
|
||||
// When `FileMetaData.user_defined_timestamps_persisted` is false and
|
||||
// user-defined timestamp size is non-zero. User-defined timestamps are
|
||||
// stripped from file boundaries: `smallest`, `largest` in
|
||||
// `VersionEdit.DecodeFrom` before they were written to Manifest.
|
||||
// This is the mirroring change to handle file boundaries on the Manifest read
|
||||
// path for this scenario: to pad a minimum timestamp to the user key in
|
||||
// `smallest` and `largest` so their format are consistent with the running
|
||||
// user comparator.
|
||||
Status MaybeHandleFileBoundariesForNewFiles(VersionEdit& edit,
|
||||
const ColumnFamilyData* cfd);
|
||||
};
|
||||
|
||||
// A class similar to its base class, i.e. VersionEditHandler.
|
||||
|
||||
+66
-7
@@ -21,12 +21,20 @@
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
static void TestEncodeDecode(const VersionEdit& edit) {
|
||||
// Encoding one `VersionEdit` and decoding it again should result in the
|
||||
// exact same `VersionEdit`. However, a special handling is applied to file
|
||||
// boundaries: `FileMetaData.smallest`, `FileMetaData.largest` when
|
||||
// user-defined timestamps should not be persisted. In that scenario, this
|
||||
// invariant does not hold. We disable this scenario in this util method to
|
||||
// enable all other test cases continue to verify this invariant, while the
|
||||
// special case is separately covered in test
|
||||
// `EncodeDecodeNewFile4HandleFileBoundary`.
|
||||
std::string encoded, encoded2;
|
||||
edit.EncodeTo(&encoded);
|
||||
edit.EncodeTo(&encoded, 0 /* ts_sz */);
|
||||
VersionEdit parsed;
|
||||
Status s = parsed.DecodeFrom(encoded);
|
||||
ASSERT_TRUE(s.ok()) << s.ToString();
|
||||
parsed.EncodeTo(&encoded2);
|
||||
parsed.EncodeTo(&encoded2, 0 /* ts_sz */);
|
||||
ASSERT_EQ(encoded, encoded2);
|
||||
}
|
||||
|
||||
@@ -93,7 +101,7 @@ TEST_F(VersionEditTest, EncodeDecodeNewFile4) {
|
||||
TestEncodeDecode(edit);
|
||||
|
||||
std::string encoded, encoded2;
|
||||
edit.EncodeTo(&encoded);
|
||||
edit.EncodeTo(&encoded, 0 /* ts_sz */);
|
||||
VersionEdit parsed;
|
||||
Status s = parsed.DecodeFrom(encoded);
|
||||
ASSERT_TRUE(s.ok()) << s.ToString();
|
||||
@@ -119,6 +127,57 @@ TEST_F(VersionEditTest, EncodeDecodeNewFile4) {
|
||||
ASSERT_TRUE(new_files[3].second.user_defined_timestamps_persisted);
|
||||
}
|
||||
|
||||
TEST_F(VersionEditTest, EncodeDecodeNewFile4HandleFileBoundary) {
|
||||
static const uint64_t kBig = 1ull << 50;
|
||||
size_t ts_sz = 16;
|
||||
static std::string min_ts(ts_sz, static_cast<unsigned char>(0));
|
||||
VersionEdit edit;
|
||||
std::string smallest = "foo";
|
||||
std::string largest = "zoo";
|
||||
// In real manifest writing scenarios, one `VersionEdit` should not contain
|
||||
// files with different `user_defined_timestamps_persisted` flag value.
|
||||
// This is just for testing file boundaries handling w.r.t persisting user
|
||||
// defined timestamps during `VersionEdit` encoding.
|
||||
edit.AddFile(
|
||||
3, 300, 3, 100, InternalKey(smallest + min_ts, kBig + 500, kTypeValue),
|
||||
InternalKey(largest + min_ts, kBig + 600, kTypeDeletion), kBig + 500,
|
||||
kBig + 600, true, Temperature::kUnknown, kInvalidBlobFileNumber,
|
||||
kUnknownOldestAncesterTime, kUnknownFileCreationTime,
|
||||
300 /* epoch_number */, kUnknownFileChecksum,
|
||||
kUnknownFileChecksumFuncName, kNullUniqueId64x2,
|
||||
0 /* compensated_range_deletion_size */, 0 /* tail_size */,
|
||||
false /* user_defined_timestamps_persisted */);
|
||||
edit.AddFile(3, 300, 3, 100,
|
||||
InternalKey(smallest + min_ts, kBig + 500, kTypeValue),
|
||||
InternalKey(largest + min_ts, kBig + 600, kTypeDeletion),
|
||||
kBig + 500, kBig + 600, true, Temperature::kUnknown,
|
||||
kInvalidBlobFileNumber, kUnknownOldestAncesterTime,
|
||||
kUnknownFileCreationTime, 300 /* epoch_number */,
|
||||
kUnknownFileChecksum, kUnknownFileChecksumFuncName,
|
||||
kNullUniqueId64x2, 0 /* compensated_range_deletion_size */,
|
||||
0 /* tail_size */, true /* user_defined_timestamps_persisted */);
|
||||
|
||||
std::string encoded;
|
||||
edit.EncodeTo(&encoded, ts_sz);
|
||||
VersionEdit parsed;
|
||||
Status s = parsed.DecodeFrom(encoded);
|
||||
ASSERT_TRUE(s.ok()) << s.ToString();
|
||||
auto& new_files = parsed.GetNewFiles();
|
||||
ASSERT_TRUE(new_files.size() == 2);
|
||||
ASSERT_FALSE(new_files[0].second.user_defined_timestamps_persisted);
|
||||
// First file's boundaries do not contain user-defined timestamps.
|
||||
ASSERT_EQ(InternalKey(smallest, kBig + 500, kTypeValue).Encode(),
|
||||
new_files[0].second.smallest.Encode());
|
||||
ASSERT_EQ(InternalKey(largest, kBig + 600, kTypeDeletion).Encode(),
|
||||
new_files[0].second.largest.Encode());
|
||||
ASSERT_TRUE(new_files[1].second.user_defined_timestamps_persisted);
|
||||
// Second file's boundaries contain user-defined timestamps.
|
||||
ASSERT_EQ(InternalKey(smallest + min_ts, kBig + 500, kTypeValue).Encode(),
|
||||
new_files[1].second.smallest.Encode());
|
||||
ASSERT_EQ(InternalKey(largest + min_ts, kBig + 600, kTypeDeletion).Encode(),
|
||||
new_files[1].second.largest.Encode());
|
||||
}
|
||||
|
||||
TEST_F(VersionEditTest, ForwardCompatibleNewFile4) {
|
||||
static const uint64_t kBig = 1ull << 50;
|
||||
VersionEdit edit;
|
||||
@@ -158,7 +217,7 @@ TEST_F(VersionEditTest, ForwardCompatibleNewFile4) {
|
||||
}
|
||||
});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
edit.EncodeTo(&encoded);
|
||||
edit.EncodeTo(&encoded, 0 /* ts_sz */);
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
|
||||
VersionEdit parsed;
|
||||
@@ -198,7 +257,7 @@ TEST_F(VersionEditTest, NewFile4NotSupportedField) {
|
||||
PutLengthPrefixedSlice(str, str1);
|
||||
});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
edit.EncodeTo(&encoded);
|
||||
edit.EncodeTo(&encoded, 0 /* ts_sz */);
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
|
||||
VersionEdit parsed;
|
||||
@@ -214,7 +273,7 @@ TEST_F(VersionEditTest, EncodeEmptyFile) {
|
||||
1 /*epoch_number*/, kUnknownFileChecksum,
|
||||
kUnknownFileChecksumFuncName, kNullUniqueId64x2, 0, 0, true);
|
||||
std::string buffer;
|
||||
ASSERT_TRUE(!edit.EncodeTo(&buffer));
|
||||
ASSERT_TRUE(!edit.EncodeTo(&buffer, 0 /* ts_sz */));
|
||||
}
|
||||
|
||||
TEST_F(VersionEditTest, ColumnFamilyTest) {
|
||||
@@ -583,7 +642,7 @@ TEST_F(VersionEditTest, IgnorableTags) {
|
||||
edit.SetColumnFamily(kColumnFamilyId);
|
||||
|
||||
std::string encoded;
|
||||
ASSERT_TRUE(edit.EncodeTo(&encoded));
|
||||
ASSERT_TRUE(edit.EncodeTo(&encoded, 0 /* ts_sz */));
|
||||
|
||||
VersionEdit decoded;
|
||||
ASSERT_OK(decoded.DecodeFrom(encoded));
|
||||
|
||||
+36
-11
@@ -5118,6 +5118,12 @@ Status VersionSet::ProcessManifestWrites(
|
||||
assert(manifest_writers_.front() == &first_writer);
|
||||
|
||||
autovector<VersionEdit*> batch_edits;
|
||||
// This vector keeps track of the corresponding user-defined timestamp size
|
||||
// for `batch_edits` side by side, which is only needed for encoding a
|
||||
// `VersionEdit` that adds new SST files.
|
||||
// Note that anytime `batch_edits` has new element added or get existing
|
||||
// element removed, `batch_edits_ts_sz` should be updated too.
|
||||
autovector<std::optional<size_t>> batch_edits_ts_sz;
|
||||
autovector<Version*> versions;
|
||||
autovector<const MutableCFOptions*> mutable_cf_options_ptrs;
|
||||
std::vector<std::unique_ptr<BaseReferencedVersionBuilder>> builder_guards;
|
||||
@@ -5133,6 +5139,7 @@ Status VersionSet::ProcessManifestWrites(
|
||||
// No group commits for column family add or drop
|
||||
LogAndApplyCFHelper(first_writer.edit_list.front(), &max_last_sequence);
|
||||
batch_edits.push_back(first_writer.edit_list.front());
|
||||
batch_edits_ts_sz.push_back(std::nullopt);
|
||||
} else {
|
||||
auto it = manifest_writers_.cbegin();
|
||||
size_t group_start = std::numeric_limits<size_t>::max();
|
||||
@@ -5209,6 +5216,9 @@ Status VersionSet::ProcessManifestWrites(
|
||||
TEST_SYNC_POINT_CALLBACK("VersionSet::ProcessManifestWrites:NewVersion",
|
||||
version);
|
||||
}
|
||||
const Comparator* ucmp = last_writer->cfd->user_comparator();
|
||||
assert(ucmp);
|
||||
std::optional<size_t> edit_ts_sz = ucmp->timestamp_size();
|
||||
for (const auto& e : last_writer->edit_list) {
|
||||
if (e->is_in_atomic_group_) {
|
||||
if (batch_edits.empty() || !batch_edits.back()->is_in_atomic_group_ ||
|
||||
@@ -5229,6 +5239,7 @@ Status VersionSet::ProcessManifestWrites(
|
||||
return s;
|
||||
}
|
||||
batch_edits.push_back(e);
|
||||
batch_edits_ts_sz.push_back(edit_ts_sz);
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < static_cast<int>(versions.size()); ++i) {
|
||||
@@ -5394,9 +5405,11 @@ Status VersionSet::ProcessManifestWrites(
|
||||
#ifndef NDEBUG
|
||||
size_t idx = 0;
|
||||
#endif
|
||||
for (auto& e : batch_edits) {
|
||||
assert(batch_edits.size() == batch_edits_ts_sz.size());
|
||||
for (size_t bidx = 0; bidx < batch_edits.size(); bidx++) {
|
||||
auto& e = batch_edits[bidx];
|
||||
std::string record;
|
||||
if (!e->EncodeTo(&record)) {
|
||||
if (!e->EncodeTo(&record, batch_edits_ts_sz[bidx])) {
|
||||
s = Status::Corruption("Unable to encode VersionEdit:" +
|
||||
e->DebugString(true));
|
||||
break;
|
||||
@@ -6289,8 +6302,9 @@ Status VersionSet::GetLiveFilesChecksumInfo(FileChecksumList* checksum_list) {
|
||||
return s;
|
||||
}
|
||||
|
||||
Status VersionSet::DumpManifest(Options& options, std::string& dscname,
|
||||
bool verbose, bool hex, bool json) {
|
||||
Status VersionSet::DumpManifest(
|
||||
Options& options, std::string& dscname, bool verbose, bool hex, bool json,
|
||||
const std::vector<ColumnFamilyDescriptor>& cf_descs) {
|
||||
assert(options.env);
|
||||
// TODO: plumb Env::IOActivity
|
||||
const ReadOptions read_options;
|
||||
@@ -6316,13 +6330,22 @@ Status VersionSet::DumpManifest(Options& options, std::string& dscname,
|
||||
std::move(file), dscname, db_options_->log_readahead_size, io_tracer_);
|
||||
}
|
||||
|
||||
std::vector<ColumnFamilyDescriptor> cf_descs;
|
||||
std::map<std::string, const ColumnFamilyDescriptor*> cf_name_to_desc;
|
||||
for (const auto& cf_desc : cf_descs) {
|
||||
cf_name_to_desc[cf_desc.name] = &cf_desc;
|
||||
}
|
||||
std::vector<ColumnFamilyDescriptor> final_cf_descs;
|
||||
for (const auto& cf : column_families) {
|
||||
cf_descs.emplace_back(cf, options);
|
||||
const auto iter = cf_name_to_desc.find(cf);
|
||||
if (iter != cf_name_to_desc.cend()) {
|
||||
final_cf_descs.push_back(*iter->second);
|
||||
} else {
|
||||
final_cf_descs.emplace_back(cf, options);
|
||||
}
|
||||
}
|
||||
|
||||
DumpManifestHandler handler(cf_descs, this, io_tracer_, read_options, verbose,
|
||||
hex, json);
|
||||
DumpManifestHandler handler(final_cf_descs, this, io_tracer_, read_options,
|
||||
verbose, hex, json);
|
||||
{
|
||||
VersionSet::LogReporter reporter;
|
||||
reporter.status = &s;
|
||||
@@ -6505,8 +6528,10 @@ Status VersionSet::WriteCurrentStateToManifest(
|
||||
|
||||
edit.SetLastSequence(descriptor_last_sequence_);
|
||||
|
||||
const Comparator* ucmp = cfd->user_comparator();
|
||||
assert(ucmp);
|
||||
std::string record;
|
||||
if (!edit.EncodeTo(&record)) {
|
||||
if (!edit.EncodeTo(&record, ucmp->timestamp_size())) {
|
||||
return Status::Corruption("Unable to Encode VersionEdit:" +
|
||||
edit.DebugString(true));
|
||||
}
|
||||
@@ -6858,7 +6883,7 @@ InternalIterator* VersionSet::MakeInputIterator(
|
||||
const FileMetaData& fmd = *flevel->files[i].file_metadata;
|
||||
if (start.has_value() &&
|
||||
cfd->user_comparator()->CompareWithoutTimestamp(
|
||||
start.value(), fmd.largest.user_key()) > 0) {
|
||||
*start, fmd.largest.user_key()) > 0) {
|
||||
continue;
|
||||
}
|
||||
// We should be able to filter out the case where the end key
|
||||
@@ -6866,7 +6891,7 @@ InternalIterator* VersionSet::MakeInputIterator(
|
||||
// We try to be extra safe here.
|
||||
if (end.has_value() &&
|
||||
cfd->user_comparator()->CompareWithoutTimestamp(
|
||||
end.value(), fmd.smallest.user_key()) < 0) {
|
||||
*end, fmd.smallest.user_key()) < 0) {
|
||||
continue;
|
||||
}
|
||||
TruncatedRangeDelIterator* range_tombstone_iter = nullptr;
|
||||
|
||||
+2
-2
@@ -1273,8 +1273,8 @@ class VersionSet {
|
||||
|
||||
// printf contents (for debugging)
|
||||
Status DumpManifest(Options& options, std::string& manifestFileName,
|
||||
bool verbose, bool hex = false, bool json = false);
|
||||
|
||||
bool verbose, bool hex = false, bool json = false,
|
||||
const std::vector<ColumnFamilyDescriptor>& cf_descs = {});
|
||||
|
||||
const std::string& DbSessionId() const { return db_session_id_; }
|
||||
|
||||
|
||||
@@ -3320,7 +3320,7 @@ class VersionSetTestMissingFiles : public VersionSetTestBase,
|
||||
++last_seqno_;
|
||||
assert(log_writer_.get() != nullptr);
|
||||
std::string record;
|
||||
ASSERT_TRUE(edit.EncodeTo(&record));
|
||||
ASSERT_TRUE(edit.EncodeTo(&record, 0 /* ts_sz */));
|
||||
Status s = log_writer_->AddRecord(record);
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
|
||||
Vendored
+11
-165
@@ -30,14 +30,6 @@ std::shared_ptr<EncryptionProvider> EncryptionProvider::NewCTRProvider(
|
||||
return std::make_shared<CTREncryptionProvider>(cipher);
|
||||
}
|
||||
|
||||
// Read up to "n" bytes from the file. "scratch[0..n-1]" may be
|
||||
// written by this routine. Sets "*result" to the data that was
|
||||
// read (including if fewer than "n" bytes were successfully read).
|
||||
// May set "*result" to point at data in "scratch[0..n-1]", so
|
||||
// "scratch[0..n-1]" must be live when "*result" is used.
|
||||
// If an error was encountered, returns a non-OK status.
|
||||
//
|
||||
// REQUIRES: External synchronization
|
||||
IOStatus EncryptedSequentialFile::Read(size_t n, const IOOptions& options,
|
||||
Slice* result, char* scratch,
|
||||
IODebugContext* dbg) {
|
||||
@@ -52,19 +44,12 @@ IOStatus EncryptedSequentialFile::Read(size_t n, const IOOptions& options,
|
||||
stream_->Decrypt(offset_, (char*)result->data(), result->size()));
|
||||
}
|
||||
if (io_s.ok()) {
|
||||
offset_ += result->size(); // We've already ready data from disk, so update
|
||||
offset_ += result->size(); // We've already read data from disk, so update
|
||||
// offset_ even if decryption fails.
|
||||
}
|
||||
return io_s;
|
||||
}
|
||||
|
||||
// Skip "n" bytes from the file. This is guaranteed to be no
|
||||
// slower that reading the same data, but may be faster.
|
||||
//
|
||||
// If end of file is reached, skipping will stop at the end of the
|
||||
// file, and Skip will return OK.
|
||||
//
|
||||
// REQUIRES: External synchronization
|
||||
IOStatus EncryptedSequentialFile::Skip(uint64_t n) {
|
||||
auto status = file_->Skip(n);
|
||||
if (!status.ok()) {
|
||||
@@ -74,28 +59,19 @@ IOStatus EncryptedSequentialFile::Skip(uint64_t n) {
|
||||
return status;
|
||||
}
|
||||
|
||||
// Indicates the upper layers if the current SequentialFile implementation
|
||||
// uses direct IO.
|
||||
bool EncryptedSequentialFile::use_direct_io() const {
|
||||
return file_->use_direct_io();
|
||||
}
|
||||
|
||||
// Use the returned alignment value to allocate
|
||||
// aligned buffer for Direct I/O
|
||||
size_t EncryptedSequentialFile::GetRequiredBufferAlignment() const {
|
||||
return file_->GetRequiredBufferAlignment();
|
||||
}
|
||||
|
||||
// Remove any kind of caching of data from the offset to offset+length
|
||||
// of this file. If the length is 0, then it refers to the end of file.
|
||||
// If the system is not caching the file contents, then this is a noop.
|
||||
IOStatus EncryptedSequentialFile::InvalidateCache(size_t offset,
|
||||
size_t length) {
|
||||
return file_->InvalidateCache(offset + prefixLength_, length);
|
||||
}
|
||||
|
||||
// Positioned Read for direct I/O
|
||||
// If Direct I/O enabled, offset, n, and scratch should be properly aligned
|
||||
IOStatus EncryptedSequentialFile::PositionedRead(uint64_t offset, size_t n,
|
||||
const IOOptions& options,
|
||||
Slice* result, char* scratch,
|
||||
@@ -115,16 +91,6 @@ IOStatus EncryptedSequentialFile::PositionedRead(uint64_t offset, size_t n,
|
||||
return io_s;
|
||||
}
|
||||
|
||||
// Read up to "n" bytes from the file starting at "offset".
|
||||
// "scratch[0..n-1]" may be written by this routine. Sets "*result"
|
||||
// to the data that was read (including if fewer than "n" bytes were
|
||||
// successfully read). May set "*result" to point at data in
|
||||
// "scratch[0..n-1]", so "scratch[0..n-1]" must be live when
|
||||
// "*result" is used. If an error was encountered, returns a non-OK
|
||||
// status.
|
||||
//
|
||||
// Safe for concurrent use by multiple threads.
|
||||
// If Direct I/O enabled, offset, n, and scratch should be aligned properly.
|
||||
IOStatus EncryptedRandomAccessFile::Read(uint64_t offset, size_t n,
|
||||
const IOOptions& options,
|
||||
Slice* result, char* scratch,
|
||||
@@ -143,29 +109,12 @@ IOStatus EncryptedRandomAccessFile::Read(uint64_t offset, size_t n,
|
||||
return io_s;
|
||||
}
|
||||
|
||||
// Readahead the file starting from offset by n bytes for caching.
|
||||
IOStatus EncryptedRandomAccessFile::Prefetch(uint64_t offset, size_t n,
|
||||
const IOOptions& options,
|
||||
IODebugContext* dbg) {
|
||||
// return Status::OK();
|
||||
return file_->Prefetch(offset + prefixLength_, n, options, dbg);
|
||||
}
|
||||
|
||||
// Tries to get an unique ID for this file that will be the same each time
|
||||
// the file is opened (and will stay the same while the file is open).
|
||||
// Furthermore, it tries to make this ID at most "max_size" bytes. If such an
|
||||
// ID can be created this function returns the length of the ID and places it
|
||||
// in "id"; otherwise, this function returns 0, in which case "id"
|
||||
// may not have been modified.
|
||||
//
|
||||
// This function guarantees, for IDs from a given environment, two unique ids
|
||||
// cannot be made equal to each other by adding arbitrary bytes to one of
|
||||
// them. That is, no unique ID is the prefix of another.
|
||||
//
|
||||
// This function guarantees that the returned ID will not be interpretable as
|
||||
// a single varint.
|
||||
//
|
||||
// Note: these IDs are only valid for the duration of the process.
|
||||
size_t EncryptedRandomAccessFile::GetUniqueId(char* id, size_t max_size) const {
|
||||
return file_->GetUniqueId(id, max_size);
|
||||
};
|
||||
@@ -174,29 +123,19 @@ void EncryptedRandomAccessFile::Hint(AccessPattern pattern) {
|
||||
file_->Hint(pattern);
|
||||
}
|
||||
|
||||
// Indicates the upper layers if the current RandomAccessFile implementation
|
||||
// uses direct IO.
|
||||
bool EncryptedRandomAccessFile::use_direct_io() const {
|
||||
return file_->use_direct_io();
|
||||
}
|
||||
|
||||
// Use the returned alignment value to allocate
|
||||
// aligned buffer for Direct I/O
|
||||
size_t EncryptedRandomAccessFile::GetRequiredBufferAlignment() const {
|
||||
return file_->GetRequiredBufferAlignment();
|
||||
}
|
||||
|
||||
// Remove any kind of caching of data from the offset to offset+length
|
||||
// of this file. If the length is 0, then it refers to the end of file.
|
||||
// If the system is not caching the file contents, then this is a noop.
|
||||
IOStatus EncryptedRandomAccessFile::InvalidateCache(size_t offset,
|
||||
size_t length) {
|
||||
return file_->InvalidateCache(offset + prefixLength_, length);
|
||||
}
|
||||
|
||||
// A file abstraction for sequential writing. The implementation
|
||||
// must provide buffering since callers may append small fragments
|
||||
// at a time to the file.
|
||||
IOStatus EncryptedWritableFile::Append(const Slice& data,
|
||||
const IOOptions& options,
|
||||
IODebugContext* dbg) {
|
||||
@@ -252,67 +191,39 @@ IOStatus EncryptedWritableFile::PositionedAppend(const Slice& data,
|
||||
return file_->PositionedAppend(dataToAppend, offset, options, dbg);
|
||||
}
|
||||
|
||||
// Indicates the upper layers if the current WritableFile implementation
|
||||
// uses direct IO.
|
||||
bool EncryptedWritableFile::use_direct_io() const {
|
||||
return file_->use_direct_io();
|
||||
}
|
||||
|
||||
// true if Sync() and Fsync() are safe to call concurrently with Append()
|
||||
// and Flush().
|
||||
bool EncryptedWritableFile::IsSyncThreadSafe() const {
|
||||
return file_->IsSyncThreadSafe();
|
||||
}
|
||||
|
||||
// Use the returned alignment value to allocate
|
||||
// aligned buffer for Direct I/O
|
||||
size_t EncryptedWritableFile::GetRequiredBufferAlignment() const {
|
||||
return file_->GetRequiredBufferAlignment();
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the size of valid data in the file.
|
||||
*/
|
||||
uint64_t EncryptedWritableFile::GetFileSize(const IOOptions& options,
|
||||
IODebugContext* dbg) {
|
||||
return file_->GetFileSize(options, dbg) - prefixLength_;
|
||||
}
|
||||
|
||||
// Truncate is necessary to trim the file to the correct size
|
||||
// before closing. It is not always possible to keep track of the file
|
||||
// size due to whole pages writes. The behavior is undefined if called
|
||||
// with other writes to follow.
|
||||
IOStatus EncryptedWritableFile::Truncate(uint64_t size,
|
||||
const IOOptions& options,
|
||||
IODebugContext* dbg) {
|
||||
return file_->Truncate(size + prefixLength_, options, dbg);
|
||||
}
|
||||
|
||||
// Remove any kind of caching of data from the offset to offset+length
|
||||
// of this file. If the length is 0, then it refers to the end of file.
|
||||
// If the system is not caching the file contents, then this is a noop.
|
||||
// This call has no effect on dirty pages in the cache.
|
||||
IOStatus EncryptedWritableFile::InvalidateCache(size_t offset, size_t length) {
|
||||
return file_->InvalidateCache(offset + prefixLength_, length);
|
||||
}
|
||||
|
||||
// Sync a file range with disk.
|
||||
// offset is the starting byte of the file range to be synchronized.
|
||||
// nbytes specifies the length of the range to be synchronized.
|
||||
// This asks the OS to initiate flushing the cached data to disk,
|
||||
// without waiting for completion.
|
||||
// Default implementation does nothing.
|
||||
IOStatus EncryptedWritableFile::RangeSync(uint64_t offset, uint64_t nbytes,
|
||||
const IOOptions& options,
|
||||
IODebugContext* dbg) {
|
||||
return file_->RangeSync(offset + prefixLength_, nbytes, options, dbg);
|
||||
}
|
||||
|
||||
// PrepareWrite performs any necessary preparation for a write
|
||||
// before the write actually occurs. This allows for pre-allocation
|
||||
// of space on devices where it can result in less file
|
||||
// fragmentation and/or less waste from over-zealous filesystem
|
||||
// pre-allocation.
|
||||
void EncryptedWritableFile::PrepareWrite(size_t offset, size_t len,
|
||||
const IOOptions& options,
|
||||
IODebugContext* dbg) {
|
||||
@@ -330,7 +241,6 @@ void EncryptedWritableFile::GetPreallocationStatus(
|
||||
file_->GetPreallocationStatus(block_size, last_allocated_block);
|
||||
}
|
||||
|
||||
// Pre-allocates space for a file.
|
||||
IOStatus EncryptedWritableFile::Allocate(uint64_t offset, uint64_t len,
|
||||
const IOOptions& options,
|
||||
IODebugContext* dbg) {
|
||||
@@ -352,22 +262,14 @@ IOStatus EncryptedWritableFile::Close(const IOOptions& options,
|
||||
return file_->Close(options, dbg);
|
||||
}
|
||||
|
||||
// A file abstraction for random reading and writing.
|
||||
|
||||
// Indicates if the class makes use of direct I/O
|
||||
// If false you must pass aligned buffer to Write()
|
||||
bool EncryptedRandomRWFile::use_direct_io() const {
|
||||
return file_->use_direct_io();
|
||||
}
|
||||
|
||||
// Use the returned alignment value to allocate
|
||||
// aligned buffer for Direct I/O
|
||||
size_t EncryptedRandomRWFile::GetRequiredBufferAlignment() const {
|
||||
return file_->GetRequiredBufferAlignment();
|
||||
}
|
||||
|
||||
// Write bytes in `data` at offset `offset`, Returns Status::OK() on success.
|
||||
// Pass aligned buffer when use_direct_io() returns true.
|
||||
IOStatus EncryptedRandomRWFile::Write(uint64_t offset, const Slice& data,
|
||||
const IOOptions& options,
|
||||
IODebugContext* dbg) {
|
||||
@@ -394,9 +296,6 @@ IOStatus EncryptedRandomRWFile::Write(uint64_t offset, const Slice& data,
|
||||
return file_->Write(offset, dataToWrite, options, dbg);
|
||||
}
|
||||
|
||||
// Read up to `n` bytes starting from offset `offset` and store them in
|
||||
// result, provided `scratch` size should be at least `n`.
|
||||
// Returns Status::OK() on success.
|
||||
IOStatus EncryptedRandomRWFile::Read(uint64_t offset, size_t n,
|
||||
const IOOptions& options, Slice* result,
|
||||
char* scratch, IODebugContext* dbg) const {
|
||||
@@ -678,7 +577,6 @@ class EncryptedFileSystemImpl : public EncryptedFileSystem {
|
||||
return provider_->AddCipher(descriptor, cipher, len, for_write);
|
||||
}
|
||||
|
||||
// NewSequentialFile opens a file for sequential reading.
|
||||
IOStatus NewSequentialFile(const std::string& fname,
|
||||
const FileOptions& options,
|
||||
std::unique_ptr<FSSequentialFile>* result,
|
||||
@@ -716,7 +614,6 @@ class EncryptedFileSystemImpl : public EncryptedFileSystem {
|
||||
return status;
|
||||
}
|
||||
|
||||
// NewRandomAccessFile opens a file for random read access.
|
||||
IOStatus NewRandomAccessFile(const std::string& fname,
|
||||
const FileOptions& options,
|
||||
std::unique_ptr<FSRandomAccessFile>* result,
|
||||
@@ -747,7 +644,6 @@ class EncryptedFileSystemImpl : public EncryptedFileSystem {
|
||||
return status;
|
||||
}
|
||||
|
||||
// NewWritableFile opens a file for sequential writing.
|
||||
IOStatus NewWritableFile(const std::string& fname, const FileOptions& options,
|
||||
std::unique_ptr<FSWritableFile>* result,
|
||||
IODebugContext* dbg) override {
|
||||
@@ -765,13 +661,6 @@ class EncryptedFileSystemImpl : public EncryptedFileSystem {
|
||||
return CreateWritableEncryptedFile(fname, underlying, options, result, dbg);
|
||||
}
|
||||
|
||||
// Create an object that writes to a new file with the specified
|
||||
// name. Deletes any existing file with the same name and creates a
|
||||
// new file. On success, stores a pointer to the new file in
|
||||
// *result and returns OK. On failure stores nullptr in *result and
|
||||
// returns non-OK.
|
||||
//
|
||||
// The returned file will only be accessed by one thread at a time.
|
||||
IOStatus ReopenWritableFile(const std::string& fname,
|
||||
const FileOptions& options,
|
||||
std::unique_ptr<FSWritableFile>* result,
|
||||
@@ -790,7 +679,6 @@ class EncryptedFileSystemImpl : public EncryptedFileSystem {
|
||||
return CreateWritableEncryptedFile(fname, underlying, options, result, dbg);
|
||||
}
|
||||
|
||||
// Reuse an existing file by renaming it and opening it as writable.
|
||||
IOStatus ReuseWritableFile(const std::string& fname,
|
||||
const std::string& old_fname,
|
||||
const FileOptions& options,
|
||||
@@ -810,11 +698,6 @@ class EncryptedFileSystemImpl : public EncryptedFileSystem {
|
||||
return CreateWritableEncryptedFile(fname, underlying, options, result, dbg);
|
||||
}
|
||||
|
||||
// Open `fname` for random read and write, if file doesn't exist the file
|
||||
// will be created. On success, stores a pointer to the new file in
|
||||
// *result and returns OK. On failure returns non-OK.
|
||||
//
|
||||
// The returned file will only be accessed by one thread at a time.
|
||||
IOStatus NewRandomRWFile(const std::string& fname, const FileOptions& options,
|
||||
std::unique_ptr<FSRandomRWFile>* result,
|
||||
IODebugContext* dbg) override {
|
||||
@@ -854,20 +737,6 @@ class EncryptedFileSystemImpl : public EncryptedFileSystem {
|
||||
return status;
|
||||
}
|
||||
|
||||
// Store in *result the attributes of the children of the specified
|
||||
// directory.
|
||||
// In case the implementation lists the directory prior to iterating the
|
||||
// files
|
||||
// and files are concurrently deleted, the deleted files will be omitted
|
||||
// from
|
||||
// result.
|
||||
// The name attributes are relative to "dir".
|
||||
// Original contents of *results are dropped.
|
||||
// Returns OK if "dir" exists and "*result" contains its children.
|
||||
// NotFound if "dir" does not exist, the calling process does not
|
||||
// have
|
||||
// permission to access "dir", or if "dir" is invalid.
|
||||
// IOError if an IO Error was encountered
|
||||
IOStatus GetChildrenFileAttributes(const std::string& dir,
|
||||
const IOOptions& options,
|
||||
std::vector<FileAttributes>* result,
|
||||
@@ -894,7 +763,6 @@ class EncryptedFileSystemImpl : public EncryptedFileSystem {
|
||||
return IOStatus::OK();
|
||||
}
|
||||
|
||||
// Store the size of fname in *file_size.
|
||||
IOStatus GetFileSize(const std::string& fname, const IOOptions& options,
|
||||
uint64_t* file_size, IODebugContext* dbg) override {
|
||||
auto status =
|
||||
@@ -940,16 +808,13 @@ std::shared_ptr<FileSystem> NewEncryptedFS(
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
// Returns an Env that encrypts data when stored on disk and decrypts data when
|
||||
// read from disk.
|
||||
|
||||
Env* NewEncryptedEnv(Env* base_env,
|
||||
const std::shared_ptr<EncryptionProvider>& provider) {
|
||||
return new CompositeEnvWrapper(
|
||||
base_env, NewEncryptedFS(base_env->GetFileSystem(), provider));
|
||||
}
|
||||
|
||||
// Encrypt one or more (partial) blocks of data at the file offset.
|
||||
// Length of data is given in dataSize.
|
||||
Status BlockAccessCipherStream::Encrypt(uint64_t fileOffset, char* data,
|
||||
size_t dataSize) {
|
||||
// Calculate block index
|
||||
@@ -968,7 +833,7 @@ Status BlockAccessCipherStream::Encrypt(uint64_t fileOffset, char* data,
|
||||
if (n != blockSize) {
|
||||
// We're not encrypting a full block.
|
||||
// Copy data to blockBuffer
|
||||
if (!blockBuffer.get()) {
|
||||
if (!blockBuffer) {
|
||||
// Allocate buffer
|
||||
blockBuffer = std::unique_ptr<char[]>(new char[blockSize]);
|
||||
}
|
||||
@@ -994,8 +859,6 @@ Status BlockAccessCipherStream::Encrypt(uint64_t fileOffset, char* data,
|
||||
}
|
||||
}
|
||||
|
||||
// Decrypt one or more (partial) blocks of data at the file offset.
|
||||
// Length of data is given in dataSize.
|
||||
Status BlockAccessCipherStream::Decrypt(uint64_t fileOffset, char* data,
|
||||
size_t dataSize) {
|
||||
// Calculate block index
|
||||
@@ -1014,7 +877,7 @@ Status BlockAccessCipherStream::Decrypt(uint64_t fileOffset, char* data,
|
||||
if (n != blockSize) {
|
||||
// We're not decrypting a full block.
|
||||
// Copy data to blockBuffer
|
||||
if (!blockBuffer.get()) {
|
||||
if (!blockBuffer) {
|
||||
// Allocate buffer
|
||||
blockBuffer = std::unique_ptr<char[]>(new char[blockSize]);
|
||||
}
|
||||
@@ -1055,6 +918,7 @@ static std::unordered_map<std::string, OptionTypeInfo>
|
||||
{0 /* No offset, whole struct*/, OptionType::kInt,
|
||||
OptionVerificationType::kNormal, OptionTypeFlags::kNone}},
|
||||
};
|
||||
|
||||
// Implements a BlockCipher using ROT13.
|
||||
//
|
||||
// Note: This is a sample implementation of BlockCipher,
|
||||
@@ -1071,22 +935,17 @@ class ROT13BlockCipher : public BlockCipher {
|
||||
|
||||
static const char* kClassName() { return "ROT13"; }
|
||||
const char* Name() const override { return kClassName(); }
|
||||
// BlockSize returns the size of each block supported by this cipher stream.
|
||||
size_t BlockSize() override { return blockSize_; }
|
||||
|
||||
// Encrypt a block of data.
|
||||
// Length of data is equal to BlockSize().
|
||||
size_t BlockSize() override { return blockSize_; }
|
||||
Status Encrypt(char* data) override {
|
||||
for (size_t i = 0; i < blockSize_; ++i) {
|
||||
data[i] += 13;
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
// Decrypt a block of data.
|
||||
// Length of data is equal to BlockSize().
|
||||
Status Decrypt(char* data) override { return Encrypt(data); }
|
||||
};
|
||||
|
||||
static const std::unordered_map<std::string, OptionTypeInfo>
|
||||
ctr_encryption_provider_type_info = {
|
||||
{"cipher",
|
||||
@@ -1096,14 +955,11 @@ static const std::unordered_map<std::string, OptionTypeInfo>
|
||||
};
|
||||
} // anonymous namespace
|
||||
|
||||
// Allocate scratch space which is passed to EncryptBlock/DecryptBlock.
|
||||
void CTRCipherStream::AllocateScratch(std::string& scratch) {
|
||||
auto blockSize = cipher_->BlockSize();
|
||||
scratch.reserve(blockSize);
|
||||
}
|
||||
|
||||
// Encrypt a block of data at the given block index.
|
||||
// Length of data is equal to BlockSize();
|
||||
Status CTRCipherStream::EncryptBlock(uint64_t blockIndex, char* data,
|
||||
char* scratch) {
|
||||
// Create nonce + counter
|
||||
@@ -1111,7 +967,7 @@ Status CTRCipherStream::EncryptBlock(uint64_t blockIndex, char* data,
|
||||
memmove(scratch, iv_.data(), blockSize);
|
||||
EncodeFixed64(scratch, blockIndex + initialCounter_);
|
||||
|
||||
// Encrypt nonce+counter
|
||||
// Encrypt nonce + counter
|
||||
auto status = cipher_->Encrypt(scratch);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
@@ -1124,8 +980,6 @@ Status CTRCipherStream::EncryptBlock(uint64_t blockIndex, char* data,
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
// Decrypt a block of data at the given block index.
|
||||
// Length of data is equal to BlockSize();
|
||||
Status CTRCipherStream::DecryptBlock(uint64_t blockIndex, char* data,
|
||||
char* scratch) {
|
||||
// For CTR decryption & encryption are the same
|
||||
@@ -1147,10 +1001,6 @@ bool CTREncryptionProvider::IsInstanceOf(const std::string& name) const {
|
||||
}
|
||||
}
|
||||
|
||||
// GetPrefixLength returns the length of the prefix that is added to every file
|
||||
// and used for storing encryption options.
|
||||
// For optimal performance, the prefix length should be a multiple of
|
||||
// the page size.
|
||||
size_t CTREncryptionProvider::GetPrefixLength() const {
|
||||
return defaultPrefixLength;
|
||||
}
|
||||
@@ -1179,8 +1029,6 @@ static void decodeCTRParameters(const char* prefix, size_t blockSize,
|
||||
iv = Slice(prefix + blockSize, blockSize);
|
||||
}
|
||||
|
||||
// CreateNewPrefix initialized an allocated block of prefix memory
|
||||
// for a new file.
|
||||
Status CTREncryptionProvider::CreateNewPrefix(const std::string& /*fname*/,
|
||||
char* prefix,
|
||||
size_t prefixLength) const {
|
||||
@@ -1212,10 +1060,8 @@ Status CTREncryptionProvider::CreateNewPrefix(const std::string& /*fname*/,
|
||||
status = cipherStream.Encrypt(0, prefix + (2 * blockSize),
|
||||
prefixLength - (2 * blockSize));
|
||||
}
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
return Status::OK();
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
// PopulateSecretPrefixPart initializes the data into a new prefix block
|
||||
@@ -1267,7 +1113,7 @@ Status CTREncryptionProvider::CreateCipherStream(
|
||||
}
|
||||
|
||||
// CreateCipherStreamFromPrefix creates a block access cipher stream for a file
|
||||
// given given name and options. The given prefix is already decrypted.
|
||||
// given name and options. The given prefix is already decrypted.
|
||||
Status CTREncryptionProvider::CreateCipherStreamFromPrefix(
|
||||
const std::string& /*fname*/, const EnvOptions& /*options*/,
|
||||
uint64_t initialCounter, const Slice& iv, const Slice& /*prefix*/,
|
||||
|
||||
Vendored
-17
@@ -27,19 +27,13 @@ class CTRCipherStream final : public BlockAccessCipherStream {
|
||||
: cipher_(c), iv_(iv, c->BlockSize()), initialCounter_(initialCounter){};
|
||||
virtual ~CTRCipherStream(){};
|
||||
|
||||
// BlockSize returns the size of each block supported by this cipher stream.
|
||||
size_t BlockSize() override { return cipher_->BlockSize(); }
|
||||
|
||||
protected:
|
||||
// Allocate scratch space which is passed to EncryptBlock/DecryptBlock.
|
||||
void AllocateScratch(std::string&) override;
|
||||
|
||||
// Encrypt a block of data at the given block index.
|
||||
// Length of data is equal to BlockSize();
|
||||
Status EncryptBlock(uint64_t blockIndex, char* data, char* scratch) override;
|
||||
|
||||
// Decrypt a block of data at the given block index.
|
||||
// Length of data is equal to BlockSize();
|
||||
Status DecryptBlock(uint64_t blockIndex, char* data, char* scratch) override;
|
||||
};
|
||||
|
||||
@@ -66,20 +60,9 @@ class CTREncryptionProvider : public EncryptionProvider {
|
||||
static const char* kClassName() { return "CTR"; }
|
||||
const char* Name() const override { return kClassName(); }
|
||||
bool IsInstanceOf(const std::string& name) const override;
|
||||
// GetPrefixLength returns the length of the prefix that is added to every
|
||||
// file
|
||||
// and used for storing encryption options.
|
||||
// For optimal performance when using direct IO, the prefix length should be a
|
||||
// multiple of the page size.
|
||||
size_t GetPrefixLength() const override;
|
||||
|
||||
// CreateNewPrefix initialized an allocated block of prefix memory
|
||||
// for a new file.
|
||||
Status CreateNewPrefix(const std::string& fname, char* prefix,
|
||||
size_t prefixLength) const override;
|
||||
|
||||
// CreateCipherStream creates a block access cipher stream for a file given
|
||||
// given name and options.
|
||||
Status CreateCipherStream(
|
||||
const std::string& fname, const EnvOptions& options, Slice& prefix,
|
||||
std::unique_ptr<BlockAccessCipherStream>* result) override;
|
||||
|
||||
Vendored
+1
@@ -21,6 +21,7 @@
|
||||
#ifdef __SSE4_2__
|
||||
#ifdef _WIN32
|
||||
#include <intrin.h>
|
||||
#define _rdtsc() __rdtsc()
|
||||
#else
|
||||
#include <x86intrin.h>
|
||||
#endif
|
||||
|
||||
+36
-10
@@ -128,7 +128,14 @@ std::string BuildKey(int num, std::string postfix = "") {
|
||||
return "my_key_" + std::to_string(num) + postfix;
|
||||
}
|
||||
|
||||
// This test verifies the basic functionality of prefetching.
|
||||
// This test verifies the following basic functionalities of prefetching:
|
||||
// (1) If underline file system supports prefetch, and directIO is not enabled
|
||||
// make sure prefetch() is called and FilePrefetchBuffer is not used.
|
||||
// (2) If underline file system doesn't support prefetch, or directIO is
|
||||
// enabled, make sure prefetch() is not called and FilePrefetchBuffer is
|
||||
// used.
|
||||
// (3) Measure read bytes, hit and miss of SST's tail prefetching during table
|
||||
// open.
|
||||
TEST_P(PrefetchTest, Basic) {
|
||||
// First param is if the mockFS support_prefetch or not
|
||||
bool support_prefetch =
|
||||
@@ -144,6 +151,7 @@ TEST_P(PrefetchTest, Basic) {
|
||||
Options options;
|
||||
SetGenericOptions(env.get(), use_direct_io, options);
|
||||
options.statistics = CreateDBStatistics();
|
||||
options.compaction_readahead_size = 2 * 1024 * 1024;
|
||||
|
||||
const int kNumKeys = 1100;
|
||||
int buff_prefetch_count = 0;
|
||||
@@ -165,6 +173,7 @@ TEST_P(PrefetchTest, Basic) {
|
||||
ASSERT_OK(batch.Put(BuildKey(i), "value for range 1 key"));
|
||||
}
|
||||
ASSERT_OK(db_->Write(WriteOptions(), &batch));
|
||||
ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable());
|
||||
|
||||
// create second key range
|
||||
batch.Clear();
|
||||
@@ -172,6 +181,7 @@ TEST_P(PrefetchTest, Basic) {
|
||||
ASSERT_OK(batch.Put(BuildKey(i, "key2"), "value for range 2 key"));
|
||||
}
|
||||
ASSERT_OK(db_->Write(WriteOptions(), &batch));
|
||||
ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable());
|
||||
|
||||
// delete second key range
|
||||
batch.Clear();
|
||||
@@ -179,6 +189,20 @@ TEST_P(PrefetchTest, Basic) {
|
||||
ASSERT_OK(batch.Delete(BuildKey(i, "key2")));
|
||||
}
|
||||
ASSERT_OK(db_->Write(WriteOptions(), &batch));
|
||||
ASSERT_OK(db_->Flush(FlushOptions()));
|
||||
|
||||
// To verify SST file tail prefetch (once per file) during flush output
|
||||
// verification
|
||||
if (support_prefetch && !use_direct_io) {
|
||||
ASSERT_TRUE(fs->IsPrefetchCalled());
|
||||
ASSERT_EQ(3, fs->GetPrefetchCount());
|
||||
ASSERT_EQ(0, buff_prefetch_count);
|
||||
fs->ClearPrefetchCount();
|
||||
} else {
|
||||
ASSERT_FALSE(fs->IsPrefetchCalled());
|
||||
ASSERT_EQ(buff_prefetch_count, 3);
|
||||
buff_prefetch_count = 0;
|
||||
}
|
||||
|
||||
// compact database
|
||||
std::string start_key = BuildKey(0);
|
||||
@@ -205,25 +229,27 @@ TEST_P(PrefetchTest, Basic) {
|
||||
const uint64_t cur_table_open_prefetch_tail_hit =
|
||||
options.statistics->getTickerCount(TABLE_OPEN_PREFETCH_TAIL_HIT);
|
||||
|
||||
// To verify prefetch during compaction input read
|
||||
if (support_prefetch && !use_direct_io) {
|
||||
// If underline file system supports prefetch, and directIO is not enabled
|
||||
// make sure prefetch() is called and FilePrefetchBuffer is not used.
|
||||
ASSERT_TRUE(fs->IsPrefetchCalled());
|
||||
fs->ClearPrefetchCount();
|
||||
// To rule out false positive by the SST file tail prefetch during
|
||||
// compaction output verification
|
||||
ASSERT_GT(fs->GetPrefetchCount(), 1);
|
||||
ASSERT_EQ(0, buff_prefetch_count);
|
||||
fs->ClearPrefetchCount();
|
||||
} else {
|
||||
// If underline file system doesn't support prefetch, or directIO is
|
||||
// enabled, make sure prefetch() is not called and FilePrefetchBuffer is
|
||||
// used.
|
||||
ASSERT_FALSE(fs->IsPrefetchCalled());
|
||||
ASSERT_GT(buff_prefetch_count, 0);
|
||||
// To rule out false positive by the SST file tail prefetch during
|
||||
// compaction output verification
|
||||
ASSERT_GT(buff_prefetch_count, 1);
|
||||
buff_prefetch_count = 0;
|
||||
|
||||
ASSERT_GT(cur_table_open_prefetch_tail_read.count,
|
||||
prev_table_open_prefetch_tail_read.count);
|
||||
ASSERT_GT(cur_table_open_prefetch_tail_hit,
|
||||
prev_table_open_prefetch_tail_hit);
|
||||
ASSERT_GE(cur_table_open_prefetch_tail_miss,
|
||||
prev_table_open_prefetch_tail_miss);
|
||||
buff_prefetch_count = 0;
|
||||
}
|
||||
|
||||
// count the keys
|
||||
@@ -236,7 +262,7 @@ TEST_P(PrefetchTest, Basic) {
|
||||
(void)num_keys;
|
||||
}
|
||||
|
||||
// Make sure prefetch is called only if file system support prefetch.
|
||||
// To verify prefetch during user scan
|
||||
if (support_prefetch && !use_direct_io) {
|
||||
ASSERT_TRUE(fs->IsPrefetchCalled());
|
||||
fs->ClearPrefetchCount();
|
||||
|
||||
@@ -97,6 +97,15 @@ IOStatus RandomAccessFileReader::Read(
|
||||
|
||||
IOStatus io_s;
|
||||
uint64_t elapsed = 0;
|
||||
size_t alignment = file_->GetRequiredBufferAlignment();
|
||||
bool is_aligned = false;
|
||||
if (scratch != nullptr) {
|
||||
// Check if offset, length and buffer are aligned.
|
||||
is_aligned = (offset & (alignment - 1)) == 0 &&
|
||||
(n & (alignment - 1)) == 0 &&
|
||||
(uintptr_t(scratch) & (alignment - 1)) == 0;
|
||||
}
|
||||
|
||||
{
|
||||
StopWatch sw(clock_, stats_, hist_type_,
|
||||
(opts.io_activity != Env::IOActivity::kUnknown)
|
||||
@@ -106,8 +115,7 @@ IOStatus RandomAccessFileReader::Read(
|
||||
true /*delay_enabled*/);
|
||||
auto prev_perf_level = GetPerfLevel();
|
||||
IOSTATS_TIMER_GUARD(read_nanos);
|
||||
if (use_direct_io()) {
|
||||
size_t alignment = file_->GetRequiredBufferAlignment();
|
||||
if (use_direct_io() && is_aligned == false) {
|
||||
size_t aligned_offset =
|
||||
TruncateToPageBoundary(alignment, static_cast<size_t>(offset));
|
||||
size_t offset_advance = static_cast<size_t>(offset) - aligned_offset;
|
||||
@@ -182,9 +190,9 @@ IOStatus RandomAccessFileReader::Read(
|
||||
if (rate_limiter_->IsRateLimited(RateLimiter::OpType::kRead)) {
|
||||
sw.DelayStart();
|
||||
}
|
||||
allowed = rate_limiter_->RequestToken(n - pos, 0 /* alignment */,
|
||||
rate_limiter_priority, stats_,
|
||||
RateLimiter::OpType::kRead);
|
||||
allowed = rate_limiter_->RequestToken(
|
||||
n - pos, (use_direct_io() ? alignment : 0), rate_limiter_priority,
|
||||
stats_, RateLimiter::OpType::kRead);
|
||||
if (rate_limiter_->IsRateLimited(RateLimiter::OpType::kRead)) {
|
||||
sw.DelayStop();
|
||||
}
|
||||
|
||||
@@ -858,24 +858,23 @@ struct AdvancedColumnFamilyOptions {
|
||||
// Dynamically changeable through SetOptions() API
|
||||
bool report_bg_io_stats = false;
|
||||
|
||||
// Files containing updates older than TTL will go through the compaction
|
||||
// process. This usually happens in a cascading way so that those entries
|
||||
// will be compacted to bottommost level/file.
|
||||
// The feature is used to remove stale entries that have been deleted or
|
||||
// updated from the file system.
|
||||
// Pre-req: This needs max_open_files to be set to -1.
|
||||
// In Level: Non-bottom-level files older than TTL will go through the
|
||||
// compaction process.
|
||||
// In FIFO: Files older than TTL will be deleted.
|
||||
// This option has different meanings for different compaction styles:
|
||||
//
|
||||
// Leveled: Non-bottom-level files with all keys older than TTL will go
|
||||
// through the compaction process. This usually happens in a cascading
|
||||
// way so that those entries will be compacted to bottommost level/file.
|
||||
// The feature is used to remove stale entries that have been deleted or
|
||||
// updated from the file system.
|
||||
//
|
||||
// FIFO: Files with all keys older than TTL will be deleted. TTL is only
|
||||
// supported if option max_open_files is set to -1.
|
||||
//
|
||||
// unit: seconds. Ex: 1 day = 1 * 24 * 60 * 60
|
||||
// In FIFO, this option will have the same meaning as
|
||||
// periodic_compaction_seconds. Whichever stricter will be used.
|
||||
// 0 means disabling.
|
||||
// UINT64_MAX - 1 (0xfffffffffffffffe) is special flag to allow RocksDB to
|
||||
// pick default.
|
||||
//
|
||||
// Default: 30 days for leveled compaction + block based table. disable
|
||||
// otherwise.
|
||||
// Default: 30 days if using block based table. 0 (disable) otherwise.
|
||||
//
|
||||
// Dynamically changeable through SetOptions() API
|
||||
uint64_t ttl = 0xfffffffffffffffe;
|
||||
@@ -891,12 +890,9 @@ struct AdvancedColumnFamilyOptions {
|
||||
// age is based on the file's last modified time (given by the underlying
|
||||
// Env).
|
||||
//
|
||||
// Supported in all compaction styles.
|
||||
// Supported in leveled and universal compaction.
|
||||
// In Universal compaction, rocksdb will try to do a full compaction when
|
||||
// possible, see more in UniversalCompactionBuilder::PickPeriodicCompaction().
|
||||
// In FIFO compaction, this option has the same meaning as TTL and whichever
|
||||
// stricter will be used.
|
||||
// Pre-req: max_open_file == -1.
|
||||
// unit: seconds. Ex: 7 days = 7 * 24 * 60 * 60
|
||||
//
|
||||
// Values:
|
||||
@@ -905,9 +901,6 @@ struct AdvancedColumnFamilyOptions {
|
||||
// as needed. For now, RocksDB will change this value to 30 days
|
||||
// (i.e 30 * 24 * 60 * 60) so that every file goes through the compaction
|
||||
// process at least once every 30 days if not compacted sooner.
|
||||
// In FIFO compaction, since the option has the same meaning as ttl,
|
||||
// when this value is left default, and ttl is left to 0, 30 days will be
|
||||
// used. Otherwise, min(ttl, periodic_compaction_seconds) will be used.
|
||||
//
|
||||
// Default: UINT64_MAX - 1 (allow RocksDB to auto-tune)
|
||||
//
|
||||
@@ -1120,11 +1113,30 @@ struct AdvancedColumnFamilyOptions {
|
||||
//
|
||||
// When it's false, the user-defined timestamps will be removed from the user
|
||||
// keys when data is flushed from memtables to SST files. Other places that
|
||||
// user keys can be persisted like WAL and blob files go through a similar
|
||||
// process. Users should call `DB::IncreaseFullHistoryTsLow` to set a cutoff
|
||||
// timestamp. RocksDB refrains from flushing a memtable with data still above
|
||||
// the cutoff timestamp with best effort. When users try to read below the
|
||||
// cutoff timestamp, an error will be returned.
|
||||
// user keys can be persisted like file boundaries in file metadata and blob
|
||||
// files go through a similar process. There are two major motivations
|
||||
// for this flag:
|
||||
// 1) backward compatibility: if the user later decides to
|
||||
// disable the user-defined timestamp feature for the column family, these SST
|
||||
// files can be handled by a user comparator that is not aware of user-defined
|
||||
// timestamps.
|
||||
// 2) enable user-defined timestamp feature for an existing column family
|
||||
// while set this flag to be `false`: user keys in the newly generated SST
|
||||
// files are of the same format as the existing SST files.
|
||||
//
|
||||
// When setting this flag to `false`, users should also call
|
||||
// `DB::IncreaseFullHistoryTsLow` to set a cutoff timestamp for flush. RocksDB
|
||||
// refrains from flushing a memtable with data still above
|
||||
// the cutoff timestamp with best effort. Users can do user-defined
|
||||
// multi-versioned read above the cutoff timestamp. When users try to read
|
||||
// below the cutoff timestamp, an error will be returned.
|
||||
//
|
||||
// Note that if WAL is enabled, unlike SST files, user-defined timestamps are
|
||||
// persisted to WAL even if this flag is set to `false`. The benefit of this
|
||||
// is that user-defined timestamps can be recovered with the caveat that users
|
||||
// should flush all memtables so there is no active WAL files before doing a
|
||||
// downgrade or toggling on / off the user-defined timestamp feature on a
|
||||
// column family.
|
||||
//
|
||||
// Default: true (user-defined timestamps are persisted)
|
||||
// Not dynamically changeable, change it requires db restart and
|
||||
|
||||
+18
-1
@@ -411,6 +411,16 @@ rocksdb_create_column_family(rocksdb_t* db,
|
||||
const rocksdb_options_t* column_family_options,
|
||||
const char* column_family_name, char** errptr);
|
||||
|
||||
extern ROCKSDB_LIBRARY_API rocksdb_column_family_handle_t**
|
||||
rocksdb_create_column_families(rocksdb_t* db,
|
||||
const rocksdb_options_t* column_family_options,
|
||||
int num_column_families,
|
||||
const char* const* column_family_names,
|
||||
size_t* lencfs, char** errptr);
|
||||
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_create_column_families_destroy(
|
||||
rocksdb_column_family_handle_t** list);
|
||||
|
||||
extern ROCKSDB_LIBRARY_API rocksdb_column_family_handle_t*
|
||||
rocksdb_create_column_family_with_ttl(
|
||||
rocksdb_t* db, const rocksdb_options_t* column_family_options,
|
||||
@@ -1518,7 +1528,8 @@ extern ROCKSDB_LIBRARY_API void rocksdb_options_set_hash_skip_list_rep(
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_options_set_hash_link_list_rep(
|
||||
rocksdb_options_t*, size_t);
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_options_set_plain_table_factory(
|
||||
rocksdb_options_t*, uint32_t, int, double, size_t);
|
||||
rocksdb_options_t*, uint32_t, int, double, size_t, size_t, char,
|
||||
unsigned char, unsigned char);
|
||||
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_options_set_min_level_to_compress(
|
||||
rocksdb_options_t* opt, int level);
|
||||
@@ -2446,6 +2457,12 @@ extern ROCKSDB_LIBRARY_API char* rocksdb_transactiondb_property_value(
|
||||
extern ROCKSDB_LIBRARY_API int rocksdb_transactiondb_property_int(
|
||||
rocksdb_transactiondb_t* db, const char* propname, uint64_t* out_val);
|
||||
|
||||
extern ROCKSDB_LIBRARY_API rocksdb_t* rocksdb_transactiondb_get_base_db(
|
||||
rocksdb_transactiondb_t* txn_db);
|
||||
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_transactiondb_close_base_db(
|
||||
rocksdb_t* base_db);
|
||||
|
||||
extern ROCKSDB_LIBRARY_API rocksdb_transaction_t* rocksdb_transaction_begin(
|
||||
rocksdb_transactiondb_t* txn_db,
|
||||
const rocksdb_writeoptions_t* write_options,
|
||||
|
||||
+11
-11
@@ -32,18 +32,18 @@ class SecondaryCache;
|
||||
// example, HyperClockCache is not usable as a general cache because it expects
|
||||
// only fixed-size block cache keys, but this limitation is not yet reflected
|
||||
// in the API function signatures.
|
||||
// * Phase 1 (done) - Make both BlockCache and GeneralCache aliases for Cache,
|
||||
// and make a factory function for general caches. Encourage users of row_cache
|
||||
// (not common) to switch to the factory function for general caches.
|
||||
// * Phase 2 - Split off GenericCache as its own class, removing secondary
|
||||
// * Phase 1 (done) - Make both BlockCache and RowCache aliases for Cache,
|
||||
// and make a factory function for row caches. Encourage users of row_cache
|
||||
// (not common) to switch to the factory function for row caches.
|
||||
// * Phase 2 - Split off RowCache as its own class, removing secondary
|
||||
// cache support features and more from the API to simplify it. Between Phase 1
|
||||
// and Phase 2 users of row_cache will need to update their code. Any time
|
||||
// after Phase 2, the block cache API can become more specialized in ways
|
||||
// incompatible with general caches.
|
||||
// after Phase 2, the block cache and row cache APIs can become more specialized
|
||||
// in ways incompatible with general caches.
|
||||
// * Phase 3 - Move existing RocksDB uses of Cache to BlockCache, and deprecate
|
||||
// (but not yet remove) Cache as an alias for BlockCache.
|
||||
using BlockCache = Cache;
|
||||
using GeneralCache = Cache;
|
||||
using RowCache = Cache;
|
||||
|
||||
// Classifications of block cache entries.
|
||||
//
|
||||
@@ -155,7 +155,7 @@ struct ShardedCacheOptions {
|
||||
CacheMetadataChargePolicy metadata_charge_policy =
|
||||
kDefaultCacheMetadataChargePolicy;
|
||||
|
||||
// A SecondaryCache instance to use the non-volatile tier. For a GeneralCache
|
||||
// A SecondaryCache instance to use the non-volatile tier. For a RowCache
|
||||
// this option must be kept as default empty.
|
||||
std::shared_ptr<SecondaryCache> secondary_cache;
|
||||
|
||||
@@ -260,9 +260,9 @@ struct LRUCacheOptions : public ShardedCacheOptions {
|
||||
// Construct an instance of LRUCache using these options
|
||||
std::shared_ptr<Cache> MakeSharedCache() const;
|
||||
|
||||
// Construct an instance of LRUCache for use as a general cache (e.g. for
|
||||
// row_cache). Some options are not relevant to general caches.
|
||||
std::shared_ptr<GeneralCache> MakeSharedGeneralCache() const;
|
||||
// Construct an instance of LRUCache for use as a row cache, typically for
|
||||
// `DBOptions::row_cache`. Some options are not relevant to row caches.
|
||||
std::shared_ptr<RowCache> MakeSharedRowCache() const;
|
||||
};
|
||||
|
||||
// DEPRECATED wrapper function
|
||||
|
||||
@@ -67,7 +67,7 @@ class BlockAccessCipherStream {
|
||||
// including data loss, unreported corruption, deadlocks, and more.
|
||||
class BlockCipher : public Customizable {
|
||||
public:
|
||||
virtual ~BlockCipher(){};
|
||||
virtual ~BlockCipher() {}
|
||||
|
||||
// Creates a new BlockCipher from the input config_options and value
|
||||
// The value describes the type of provider (and potentially optional
|
||||
@@ -114,13 +114,13 @@ class BlockCipher : public Customizable {
|
||||
// including data loss, unreported corruption, deadlocks, and more.
|
||||
class EncryptionProvider : public Customizable {
|
||||
public:
|
||||
virtual ~EncryptionProvider(){};
|
||||
virtual ~EncryptionProvider() {}
|
||||
|
||||
// Creates a new EncryptionProvider from the input config_options and value
|
||||
// Creates a new EncryptionProvider from the input config_options and value.
|
||||
// The value describes the type of provider (and potentially optional
|
||||
// configuration parameters) used to create this provider.
|
||||
// For example, if the value is "CTR", a CTREncryptionProvider will be
|
||||
// created. If the value is ends with "://test" (e.g CTR://test"), the
|
||||
// created. If the value is end with "://test" (e.g CTR://test"), the
|
||||
// provider will be initialized in "TEST" mode prior to being returned.
|
||||
//
|
||||
// @param config_options Options to control how this provider is created
|
||||
@@ -153,7 +153,7 @@ class EncryptionProvider : public Customizable {
|
||||
size_t prefixLength) const = 0;
|
||||
|
||||
// Method to add a new cipher key for use by the EncryptionProvider.
|
||||
// @param description Descriptor for this key.
|
||||
// @param descriptor Descriptor for this key
|
||||
// @param cipher The cryptographic key to use
|
||||
// @param len The length of the cipher key
|
||||
// @param for_write If true, this cipher should be used for writing files.
|
||||
@@ -165,15 +165,15 @@ class EncryptionProvider : public Customizable {
|
||||
size_t len, bool for_write) = 0;
|
||||
|
||||
// CreateCipherStream creates a block access cipher stream for a file given
|
||||
// given name and options.
|
||||
// name and options.
|
||||
virtual Status CreateCipherStream(
|
||||
const std::string& fname, const EnvOptions& options, Slice& prefix,
|
||||
std::unique_ptr<BlockAccessCipherStream>* result) = 0;
|
||||
|
||||
// Returns a string representing an encryption marker prefix for this
|
||||
// provider. If a marker is provided, this marker can be used to tell whether
|
||||
// or not a file is encrypted by this provider. The maker will also be part
|
||||
// of any encryption prefix for this provider.
|
||||
// a file is encrypted by this provider. The marker will also be part of any
|
||||
// encryption prefix for this provider.
|
||||
virtual std::string GetMarker() const { return ""; }
|
||||
};
|
||||
|
||||
@@ -182,7 +182,7 @@ class EncryptedSequentialFile : public FSSequentialFile {
|
||||
std::unique_ptr<FSSequentialFile> file_;
|
||||
std::unique_ptr<BlockAccessCipherStream> stream_;
|
||||
uint64_t offset_;
|
||||
size_t prefixLength_;
|
||||
const size_t prefixLength_;
|
||||
|
||||
public:
|
||||
// Default ctor. Given underlying sequential file is supposed to be at
|
||||
@@ -195,47 +195,22 @@ class EncryptedSequentialFile : public FSSequentialFile {
|
||||
offset_(prefixLength),
|
||||
prefixLength_(prefixLength) {}
|
||||
|
||||
// Read up to "n" bytes from the file. "scratch[0..n-1]" may be
|
||||
// written by this routine. Sets "*result" to the data that was
|
||||
// read (including if fewer than "n" bytes were successfully read).
|
||||
// May set "*result" to point at data in "scratch[0..n-1]", so
|
||||
// "scratch[0..n-1]" must be live when "*result" is used.
|
||||
// If an error was encountered, returns a non-OK status.
|
||||
//
|
||||
// REQUIRES: External synchronization
|
||||
IOStatus Read(size_t n, const IOOptions& options, Slice* result,
|
||||
char* scratch, IODebugContext* dbg) override;
|
||||
|
||||
// Skip "n" bytes from the file. This is guaranteed to be no
|
||||
// slower that reading the same data, but may be faster.
|
||||
//
|
||||
// If end of file is reached, skipping will stop at the end of the
|
||||
// file, and Skip will return OK.
|
||||
//
|
||||
// REQUIRES: External synchronization
|
||||
IOStatus Skip(uint64_t n) override;
|
||||
|
||||
// Indicates the upper layers if the current SequentialFile implementation
|
||||
// uses direct IO.
|
||||
bool use_direct_io() const override;
|
||||
|
||||
// Use the returned alignment value to allocate
|
||||
// aligned buffer for Direct I/O
|
||||
size_t GetRequiredBufferAlignment() const override;
|
||||
|
||||
// Remove any kind of caching of data from the offset to offset+length
|
||||
// of this file. If the length is 0, then it refers to the end of file.
|
||||
// If the system is not caching the file contents, then this is a noop.
|
||||
IOStatus InvalidateCache(size_t offset, size_t length) override;
|
||||
|
||||
// Positioned Read for direct I/O
|
||||
// If Direct I/O enabled, offset, n, and scratch should be properly aligned
|
||||
IOStatus PositionedRead(uint64_t offset, size_t n, const IOOptions& options,
|
||||
Slice* result, char* scratch,
|
||||
IODebugContext* dbg) override;
|
||||
};
|
||||
|
||||
// A file abstraction for randomly reading the contents of a file.
|
||||
class EncryptedRandomAccessFile : public FSRandomAccessFile {
|
||||
protected:
|
||||
std::unique_ptr<FSRandomAccessFile> file_;
|
||||
@@ -250,60 +225,24 @@ class EncryptedRandomAccessFile : public FSRandomAccessFile {
|
||||
stream_(std::move(s)),
|
||||
prefixLength_(prefixLength) {}
|
||||
|
||||
// Read up to "n" bytes from the file starting at "offset".
|
||||
// "scratch[0..n-1]" may be written by this routine. Sets "*result"
|
||||
// to the data that was read (including if fewer than "n" bytes were
|
||||
// successfully read). May set "*result" to point at data in
|
||||
// "scratch[0..n-1]", so "scratch[0..n-1]" must be live when
|
||||
// "*result" is used. If an error was encountered, returns a non-OK
|
||||
// status.
|
||||
//
|
||||
// Safe for concurrent use by multiple threads.
|
||||
// If Direct I/O enabled, offset, n, and scratch should be aligned properly.
|
||||
IOStatus Read(uint64_t offset, size_t n, const IOOptions& options,
|
||||
Slice* result, char* scratch,
|
||||
IODebugContext* dbg) const override;
|
||||
|
||||
// Readahead the file starting from offset by n bytes for caching.
|
||||
IOStatus Prefetch(uint64_t offset, size_t n, const IOOptions& options,
|
||||
IODebugContext* dbg) override;
|
||||
|
||||
// Tries to get an unique ID for this file that will be the same each time
|
||||
// the file is opened (and will stay the same while the file is open).
|
||||
// Furthermore, it tries to make this ID at most "max_size" bytes. If such an
|
||||
// ID can be created this function returns the length of the ID and places it
|
||||
// in "id"; otherwise, this function returns 0, in which case "id"
|
||||
// may not have been modified.
|
||||
//
|
||||
// This function guarantees, for IDs from a given environment, two unique ids
|
||||
// cannot be made equal to each other by adding arbitrary bytes to one of
|
||||
// them. That is, no unique ID is the prefix of another.
|
||||
//
|
||||
// This function guarantees that the returned ID will not be interpretable as
|
||||
// a single varint.
|
||||
//
|
||||
// Note: these IDs are only valid for the duration of the process.
|
||||
size_t GetUniqueId(char* id, size_t max_size) const override;
|
||||
|
||||
void Hint(AccessPattern pattern) override;
|
||||
|
||||
// Indicates the upper layers if the current RandomAccessFile implementation
|
||||
// uses direct IO.
|
||||
bool use_direct_io() const override;
|
||||
|
||||
// Use the returned alignment value to allocate
|
||||
// aligned buffer for Direct I/O
|
||||
size_t GetRequiredBufferAlignment() const override;
|
||||
|
||||
// Remove any kind of caching of data from the offset to offset+length
|
||||
// of this file. If the length is 0, then it refers to the end of file.
|
||||
// If the system is not caching the file contents, then this is a noop.
|
||||
IOStatus InvalidateCache(size_t offset, size_t length) override;
|
||||
};
|
||||
|
||||
// A file abstraction for sequential writing. The implementation
|
||||
// must provide buffering since callers may append small fragments
|
||||
// at a time to the file.
|
||||
class EncryptedWritableFile : public FSWritableFile {
|
||||
protected:
|
||||
std::unique_ptr<FSWritableFile> file_;
|
||||
@@ -328,50 +267,22 @@ class EncryptedWritableFile : public FSWritableFile {
|
||||
const IOOptions& options,
|
||||
IODebugContext* dbg) override;
|
||||
|
||||
// true if Sync() and Fsync() are safe to call concurrently with Append()
|
||||
// and Flush().
|
||||
bool IsSyncThreadSafe() const override;
|
||||
|
||||
// Indicates the upper layers if the current WritableFile implementation
|
||||
// uses direct IO.
|
||||
bool use_direct_io() const override;
|
||||
|
||||
// Use the returned alignment value to allocate
|
||||
// aligned buffer for Direct I/O
|
||||
size_t GetRequiredBufferAlignment() const override;
|
||||
|
||||
/*
|
||||
* Get the size of valid data in the file.
|
||||
*/
|
||||
uint64_t GetFileSize(const IOOptions& options, IODebugContext* dbg) override;
|
||||
|
||||
// Truncate is necessary to trim the file to the correct size
|
||||
// before closing. It is not always possible to keep track of the file
|
||||
// size due to whole pages writes. The behavior is undefined if called
|
||||
// with other writes to follow.
|
||||
IOStatus Truncate(uint64_t size, const IOOptions& options,
|
||||
IODebugContext* dbg) override;
|
||||
|
||||
// Remove any kind of caching of data from the offset to offset+length
|
||||
// of this file. If the length is 0, then it refers to the end of file.
|
||||
// If the system is not caching the file contents, then this is a noop.
|
||||
// This call has no effect on dirty pages in the cache.
|
||||
IOStatus InvalidateCache(size_t offset, size_t length) override;
|
||||
|
||||
// Sync a file range with disk.
|
||||
// offset is the starting byte of the file range to be synchronized.
|
||||
// nbytes specifies the length of the range to be synchronized.
|
||||
// This asks the OS to initiate flushing the cached data to disk,
|
||||
// without waiting for completion.
|
||||
// Default implementation does nothing.
|
||||
IOStatus RangeSync(uint64_t offset, uint64_t nbytes, const IOOptions& options,
|
||||
IODebugContext* dbg) override;
|
||||
|
||||
// PrepareWrite performs any necessary preparation for a write
|
||||
// before the write actually occurs. This allows for pre-allocation
|
||||
// of space on devices where it can result in less file
|
||||
// fragmentation and/or less waste from over-zealous filesystem
|
||||
// pre-allocation.
|
||||
void PrepareWrite(size_t offset, size_t len, const IOOptions& options,
|
||||
IODebugContext* dbg) override;
|
||||
|
||||
@@ -380,7 +291,6 @@ class EncryptedWritableFile : public FSWritableFile {
|
||||
void GetPreallocationStatus(size_t* block_size,
|
||||
size_t* last_allocated_block) override;
|
||||
|
||||
// Pre-allocates space for a file.
|
||||
IOStatus Allocate(uint64_t offset, uint64_t len, const IOOptions& options,
|
||||
IODebugContext* dbg) override;
|
||||
|
||||
@@ -391,7 +301,6 @@ class EncryptedWritableFile : public FSWritableFile {
|
||||
IOStatus Close(const IOOptions& options, IODebugContext* dbg) override;
|
||||
};
|
||||
|
||||
// A file abstraction for random reading and writing.
|
||||
class EncryptedRandomRWFile : public FSRandomRWFile {
|
||||
protected:
|
||||
std::unique_ptr<FSRandomRWFile> file_;
|
||||
@@ -406,22 +315,13 @@ class EncryptedRandomRWFile : public FSRandomRWFile {
|
||||
stream_(std::move(s)),
|
||||
prefixLength_(prefixLength) {}
|
||||
|
||||
// Indicates if the class makes use of direct I/O
|
||||
// If false you must pass aligned buffer to Write()
|
||||
bool use_direct_io() const override;
|
||||
|
||||
// Use the returned alignment value to allocate
|
||||
// aligned buffer for Direct I/O
|
||||
size_t GetRequiredBufferAlignment() const override;
|
||||
|
||||
// Write bytes in `data` at offset `offset`, Returns Status::OK() on success.
|
||||
// Pass aligned buffer when use_direct_io() returns true.
|
||||
IOStatus Write(uint64_t offset, const Slice& data, const IOOptions& options,
|
||||
IODebugContext* dbg) override;
|
||||
|
||||
// Read up to `n` bytes starting from offset `offset` and store them in
|
||||
// result, provided `scratch` size should be at least `n`.
|
||||
// Returns Status::OK() on success.
|
||||
IOStatus Read(uint64_t offset, size_t n, const IOOptions& options,
|
||||
Slice* result, char* scratch,
|
||||
IODebugContext* dbg) const override;
|
||||
|
||||
@@ -1071,7 +1071,7 @@ struct DBOptions {
|
||||
//
|
||||
// By default, i.e., when it is false, rocksdb does not advance the sequence
|
||||
// number for new snapshots unless all the writes with lower sequence numbers
|
||||
// are already finished. This provides the immutability that we except from
|
||||
// are already finished. This provides the immutability that we expect from
|
||||
// snapshots. Moreover, since Iterator and MultiGet internally depend on
|
||||
// snapshots, the snapshot immutability results into Iterator and MultiGet
|
||||
// offering consistent-point-in-time view. If set to true, although
|
||||
@@ -1157,7 +1157,7 @@ struct DBOptions {
|
||||
|
||||
// A global cache for table-level rows.
|
||||
// Default: nullptr (disabled)
|
||||
std::shared_ptr<GeneralCache> row_cache = nullptr;
|
||||
std::shared_ptr<RowCache> row_cache = nullptr;
|
||||
|
||||
// A filter object supplied to be invoked while processing write-ahead-logs
|
||||
// (WALs) during recovery. The filter provides a way to inspect log
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
// NOTE: in 'main' development branch, this should be the *next*
|
||||
// minor or major version number planned for release.
|
||||
#define ROCKSDB_MAJOR 8
|
||||
#define ROCKSDB_MINOR 4
|
||||
#define ROCKSDB_MINOR 5
|
||||
#define ROCKSDB_PATCH 4
|
||||
|
||||
// Do not use these. We made the mistake of declaring macros starting with
|
||||
|
||||
+29
-14
@@ -31,6 +31,10 @@ void* SaveStack(int* /*num_frames*/, int /*first_frames_to_skip*/) {
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#ifdef OS_OPENBSD
|
||||
#include <sys/wait.h>
|
||||
#include <sys/sysctl.h>
|
||||
#endif // OS_OPENBSD
|
||||
#ifdef OS_FREEBSD
|
||||
#include <sys/sysctl.h>
|
||||
#endif // OS_FREEBSD
|
||||
@@ -51,21 +55,11 @@ namespace port {
|
||||
|
||||
namespace {
|
||||
|
||||
#if defined(OS_LINUX) || defined(OS_FREEBSD) || defined(OS_GNU_KFREEBSD)
|
||||
#if defined(OS_LINUX) || defined(OS_FREEBSD) || defined(OS_OPENBSD) || defined(OS_GNU_KFREEBSD)
|
||||
const char* GetExecutableName() {
|
||||
static char name[1024];
|
||||
|
||||
#if !defined(OS_FREEBSD)
|
||||
char link[1024];
|
||||
snprintf(link, sizeof(link), "/proc/%d/exe", getpid());
|
||||
auto read = readlink(link, name, sizeof(name) - 1);
|
||||
if (-1 == read) {
|
||||
return nullptr;
|
||||
} else {
|
||||
name[read] = 0;
|
||||
return name;
|
||||
}
|
||||
#else
|
||||
#if defined(OS_FREEBSD)
|
||||
int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1};
|
||||
size_t namesz = sizeof(name);
|
||||
|
||||
@@ -75,6 +69,27 @@ const char* GetExecutableName() {
|
||||
} else {
|
||||
return name;
|
||||
}
|
||||
#elif defined(OS_OPENBSD)
|
||||
int mib[4] = {CTL_KERN, KERN_PROC_ARGS, getpid(), KERN_PROC_ARGV};
|
||||
size_t namesz = sizeof(name);
|
||||
char* bin[namesz];
|
||||
|
||||
auto ret = sysctl(mib, 4, bin, &namesz, nullptr, 0);
|
||||
if (-1 == ret) {
|
||||
return nullptr;
|
||||
} else {
|
||||
return bin[0];
|
||||
}
|
||||
#else
|
||||
char link[1024];
|
||||
snprintf(link, sizeof(link), "/proc/%d/exe", getpid());
|
||||
auto read = readlink(link, name, sizeof(name) - 1);
|
||||
if (-1 == read) {
|
||||
return nullptr;
|
||||
} else {
|
||||
name[read] = 0;
|
||||
return name;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -271,7 +286,7 @@ void PrintStack(int first_frames_to_skip) {
|
||||
const int kMaxFrames = 100;
|
||||
void* frames[kMaxFrames];
|
||||
|
||||
auto num_frames = backtrace(frames, kMaxFrames);
|
||||
int num_frames = (int) backtrace(frames, kMaxFrames);
|
||||
PrintStack(&frames[first_frames_to_skip], num_frames - first_frames_to_skip);
|
||||
}
|
||||
|
||||
@@ -284,7 +299,7 @@ void* SaveStack(int* num_frames, int first_frames_to_skip) {
|
||||
const int kMaxFrames = 100;
|
||||
void* frames[kMaxFrames];
|
||||
|
||||
auto count = backtrace(frames, kMaxFrames);
|
||||
int count = (int) backtrace(frames, kMaxFrames);
|
||||
*num_frames = count - first_frames_to_skip;
|
||||
void* callstack = malloc(sizeof(void*) * *num_frames);
|
||||
memcpy(callstack, &frames[first_frames_to_skip], sizeof(void*) * *num_frames);
|
||||
|
||||
@@ -83,7 +83,7 @@ size_t TailPrefetchStats::GetSuggestedPrefetchSize() {
|
||||
//
|
||||
// and we use every of the value as a candidate, and estimate how much we
|
||||
// wasted, compared to read. For example, when we use the 3rd record
|
||||
// as candiate. This area is what we read:
|
||||
// as candidate. This area is what we read:
|
||||
// +---+
|
||||
// +---+ | |
|
||||
// | | | |
|
||||
@@ -123,7 +123,7 @@ size_t TailPrefetchStats::GetSuggestedPrefetchSize() {
|
||||
// +---+ +---+ +---+ +---+ +---+
|
||||
//
|
||||
// Which can be calculated iteratively.
|
||||
// The difference between wasted using 4st and 3rd record, will
|
||||
// The difference between wasted using 4th and 3rd record, will
|
||||
// be following area:
|
||||
// +---+
|
||||
// +--+ +-+ ++ +-+ +-+ +---+ | |
|
||||
@@ -143,8 +143,8 @@ size_t TailPrefetchStats::GetSuggestedPrefetchSize() {
|
||||
// | | | | | | | | | |
|
||||
// +---+ +---+ +---+ +---+ +---+
|
||||
//
|
||||
// which will be the size difference between 4st and 3rd record,
|
||||
// times 3, which is number of records before the 4st.
|
||||
// which will be the size difference between 4th and 3rd record,
|
||||
// times 3, which is number of records before the 4th.
|
||||
// Here we assume that all data within the prefetch range will be useful. In
|
||||
// reality, it may not be the case when a partial block is inside the range,
|
||||
// or there are data in the middle that is not read. We ignore those cases
|
||||
|
||||
@@ -53,8 +53,7 @@ BlockBuilder::BlockBuilder(
|
||||
: block_restart_interval_(block_restart_interval),
|
||||
use_delta_encoding_(use_delta_encoding),
|
||||
use_value_delta_encoding_(use_value_delta_encoding),
|
||||
ts_sz_(ts_sz),
|
||||
persist_user_defined_timestamps_(persist_user_defined_timestamps),
|
||||
strip_ts_sz_(persist_user_defined_timestamps ? 0 : ts_sz),
|
||||
is_user_key_(is_user_key),
|
||||
restarts_(1, 0), // First restart point is at offset 0
|
||||
counter_(0),
|
||||
@@ -100,8 +99,8 @@ size_t BlockBuilder::EstimateSizeAfterKV(const Slice& key,
|
||||
// Note: this is an imprecise estimate as it accounts for the whole key size
|
||||
// instead of non-shared key size.
|
||||
estimate += key.size();
|
||||
if (ts_sz_ > 0 && !persist_user_defined_timestamps_) {
|
||||
estimate -= ts_sz_;
|
||||
if (strip_ts_sz_ > 0) {
|
||||
estimate -= strip_ts_sz_;
|
||||
}
|
||||
// In value delta encoding we estimate the value delta size as half the full
|
||||
// value size since only the size field of block handle is encoded.
|
||||
@@ -175,13 +174,13 @@ void BlockBuilder::AddWithLastKey(const Slice& key, const Slice& value,
|
||||
// or Reset. This is more convenient for the caller and we can be more
|
||||
// clever inside BlockBuilder. On this hot code path, we want to avoid
|
||||
// conditional jumps like `buffer_.empty() ? ... : ...` so we can use a
|
||||
// fast min operation instead, with an assertion to be sure our logic is
|
||||
// sound.
|
||||
// fast arithmetic operation instead, with an assertion to be sure our logic
|
||||
// is sound.
|
||||
size_t buffer_size = buffer_.size();
|
||||
size_t last_key_size = last_key_param.size();
|
||||
assert(buffer_size == 0 || buffer_size >= last_key_size);
|
||||
assert(buffer_size == 0 || buffer_size >= last_key_size - strip_ts_sz_);
|
||||
|
||||
Slice last_key(last_key_param.data(), std::min(buffer_size, last_key_size));
|
||||
Slice last_key(last_key_param.data(), last_key_size * (buffer_size > 0));
|
||||
|
||||
AddWithLastKeyImpl(key, value, last_key, delta_value, buffer_size);
|
||||
}
|
||||
@@ -255,11 +254,11 @@ inline void BlockBuilder::AddWithLastKeyImpl(const Slice& key,
|
||||
const Slice BlockBuilder::MaybeStripTimestampFromKey(std::string* key_buf,
|
||||
const Slice& key) {
|
||||
Slice stripped_key = key;
|
||||
if (ts_sz_ > 0 && !persist_user_defined_timestamps_) {
|
||||
if (strip_ts_sz_ > 0) {
|
||||
if (is_user_key_) {
|
||||
stripped_key.remove_suffix(ts_sz_);
|
||||
stripped_key.remove_suffix(strip_ts_sz_);
|
||||
} else {
|
||||
StripTimestampFromInternalKey(key_buf, key, ts_sz_);
|
||||
StripTimestampFromInternalKey(key_buf, key, strip_ts_sz_);
|
||||
stripped_key = *key_buf;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,11 +94,10 @@ class BlockBuilder {
|
||||
const bool use_delta_encoding_;
|
||||
// Refer to BlockIter::DecodeCurrentValue for format of delta encoded values
|
||||
const bool use_value_delta_encoding_;
|
||||
// Size in bytes for the user-defined timestamp in a user key.
|
||||
const size_t ts_sz_;
|
||||
// Whether the user-defined timestamp part in user keys should be persisted.
|
||||
// If false, it will be stripped from the key before it's encoded.
|
||||
const bool persist_user_defined_timestamps_;
|
||||
// Size in bytes for the user-defined timestamp to strip in a user key.
|
||||
// This is non-zero if there is user-defined timestamp in the user key and it
|
||||
// should not be persisted.
|
||||
const size_t strip_ts_sz_;
|
||||
// Whether the keys provided to build this block are user keys. If not,
|
||||
// the keys are internal keys. This will affect how timestamp stripping is
|
||||
// done for the key if `persisted_user_defined_timestamps_` is false and
|
||||
|
||||
@@ -17,9 +17,30 @@ void BlockPrefetcher::PrefetchIfNeeded(
|
||||
const size_t readahead_size, bool is_for_compaction,
|
||||
const bool no_sequential_checking,
|
||||
const Env::IOPriority rate_limiter_priority) {
|
||||
// num_file_reads is used by FilePrefetchBuffer only when
|
||||
// implicit_auto_readahead is set.
|
||||
const size_t len = BlockBasedTable::BlockSizeWithTrailer(handle);
|
||||
const size_t offset = handle.offset();
|
||||
|
||||
if (is_for_compaction) {
|
||||
if (!rep->file->use_direct_io() && compaction_readahead_size_ > 0) {
|
||||
// If FS supports prefetching (readahead_limit_ will be non zero in that
|
||||
// case) and current block exists in prefetch buffer then return.
|
||||
if (offset + len <= readahead_limit_) {
|
||||
return;
|
||||
}
|
||||
Status s = rep->file->Prefetch(offset, len + compaction_readahead_size_,
|
||||
rate_limiter_priority);
|
||||
if (s.ok()) {
|
||||
readahead_limit_ = offset + len + compaction_readahead_size_;
|
||||
return;
|
||||
} else if (!s.IsNotSupported()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
// If FS prefetch is not supported, fall back to use internal prefetch
|
||||
// buffer.
|
||||
//
|
||||
// num_file_reads is used by FilePrefetchBuffer only when
|
||||
// implicit_auto_readahead is set.
|
||||
rep->CreateFilePrefetchBufferIfNotExists(
|
||||
compaction_readahead_size_, compaction_readahead_size_,
|
||||
&prefetch_buffer_, /*implicit_auto_readahead=*/false,
|
||||
@@ -60,9 +81,6 @@ void BlockPrefetcher::PrefetchIfNeeded(
|
||||
return;
|
||||
}
|
||||
|
||||
size_t len = BlockBasedTable::BlockSizeWithTrailer(handle);
|
||||
size_t offset = handle.offset();
|
||||
|
||||
// If FS supports prefetching (readahead_limit_ will be non zero in that case)
|
||||
// and current block exists in prefetch buffer then return.
|
||||
if (offset + len <= readahead_limit_) {
|
||||
@@ -98,8 +116,6 @@ void BlockPrefetcher::PrefetchIfNeeded(
|
||||
}
|
||||
|
||||
// If prefetch is not supported, fall back to use internal prefetch buffer.
|
||||
// Discarding other return status of Prefetch calls intentionally, as
|
||||
// we can fallback to reading from disk if Prefetch fails.
|
||||
Status s = rep->file->Prefetch(
|
||||
handle.offset(),
|
||||
BlockBasedTable::BlockSizeWithTrailer(handle) + readahead_size_,
|
||||
|
||||
@@ -58,11 +58,12 @@ class BlockPrefetcher {
|
||||
// lookup_context_.caller = kCompaction.
|
||||
size_t compaction_readahead_size_;
|
||||
|
||||
// readahead_size_ is used if underlying FS supports prefetching.
|
||||
// readahead_size_ is used in non-compaction read if underlying FS supports
|
||||
// prefetching.
|
||||
size_t readahead_size_;
|
||||
size_t readahead_limit_ = 0;
|
||||
// initial_auto_readahead_size_ is used if RocksDB uses internal prefetch
|
||||
// buffer.
|
||||
// initial_auto_readahead_size_ is used in non-compaction read if RocksDB uses
|
||||
// internal prefetch buffer.
|
||||
uint64_t initial_auto_readahead_size_;
|
||||
uint64_t num_file_reads_ = 0;
|
||||
uint64_t prev_offset_ = 0;
|
||||
|
||||
@@ -9,8 +9,6 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#include <cinttypes>
|
||||
#include <list>
|
||||
#include <string>
|
||||
@@ -39,9 +37,8 @@ class IndexBuilder {
|
||||
BlockBasedTableOptions::IndexType index_type,
|
||||
const ROCKSDB_NAMESPACE::InternalKeyComparator* comparator,
|
||||
const InternalKeySliceTransform* int_key_slice_transform,
|
||||
const bool use_value_delta_encoding,
|
||||
const BlockBasedTableOptions& table_opt, size_t ts_sz,
|
||||
const bool persist_user_defined_timestamps);
|
||||
bool use_value_delta_encoding, const BlockBasedTableOptions& table_opt,
|
||||
size_t ts_sz, bool persist_user_defined_timestamps);
|
||||
|
||||
// Index builder will construct a set of blocks which contain:
|
||||
// 1. One primary index block.
|
||||
@@ -51,13 +48,13 @@ class IndexBuilder {
|
||||
Slice index_block_contents;
|
||||
std::unordered_map<std::string, Slice> meta_blocks;
|
||||
};
|
||||
explicit IndexBuilder(const InternalKeyComparator* comparator, size_t ts_sz,
|
||||
bool persist_user_defined_timestamps)
|
||||
IndexBuilder(const InternalKeyComparator* comparator, size_t ts_sz,
|
||||
bool persist_user_defined_timestamps)
|
||||
: comparator_(comparator),
|
||||
ts_sz_(ts_sz),
|
||||
persist_user_defined_timestamps_(persist_user_defined_timestamps) {}
|
||||
|
||||
virtual ~IndexBuilder() {}
|
||||
virtual ~IndexBuilder() = default;
|
||||
|
||||
// Add a new index entry to index block.
|
||||
// To allow further optimization, we provide `last_key_in_current_block` and
|
||||
@@ -132,7 +129,7 @@ class IndexBuilder {
|
||||
// substitute key that serves the same function.
|
||||
class ShortenedIndexBuilder : public IndexBuilder {
|
||||
public:
|
||||
explicit ShortenedIndexBuilder(
|
||||
ShortenedIndexBuilder(
|
||||
const InternalKeyComparator* comparator,
|
||||
const int index_block_restart_interval, const uint32_t format_version,
|
||||
const bool use_value_delta_encoding,
|
||||
@@ -159,15 +156,15 @@ class ShortenedIndexBuilder : public IndexBuilder {
|
||||
seperator_is_key_plus_seq_ = (format_version <= 2);
|
||||
}
|
||||
|
||||
virtual void OnKeyAdded(const Slice& key) override {
|
||||
void OnKeyAdded(const Slice& key) override {
|
||||
if (include_first_key_ && current_block_first_internal_key_.empty()) {
|
||||
current_block_first_internal_key_.assign(key.data(), key.size());
|
||||
}
|
||||
}
|
||||
|
||||
virtual void AddIndexEntry(std::string* last_key_in_current_block,
|
||||
const Slice* first_key_in_next_block,
|
||||
const BlockHandle& block_handle) override {
|
||||
void AddIndexEntry(std::string* last_key_in_current_block,
|
||||
const Slice* first_key_in_next_block,
|
||||
const BlockHandle& block_handle) override {
|
||||
if (first_key_in_next_block != nullptr) {
|
||||
if (shortening_mode_ !=
|
||||
BlockBasedTableOptions::IndexShorteningMode::kNoShortening) {
|
||||
@@ -236,9 +233,8 @@ class ShortenedIndexBuilder : public IndexBuilder {
|
||||
}
|
||||
|
||||
using IndexBuilder::Finish;
|
||||
virtual Status Finish(
|
||||
IndexBlocks* index_blocks,
|
||||
const BlockHandle& /*last_partition_block_handle*/) override {
|
||||
Status Finish(IndexBlocks* index_blocks,
|
||||
const BlockHandle& /*last_partition_block_handle*/) override {
|
||||
if (seperator_is_key_plus_seq_) {
|
||||
index_blocks->index_block_contents = index_block_builder_.Finish();
|
||||
} else {
|
||||
@@ -249,9 +245,9 @@ class ShortenedIndexBuilder : public IndexBuilder {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
virtual size_t IndexSize() const override { return index_size_; }
|
||||
size_t IndexSize() const override { return index_size_; }
|
||||
|
||||
virtual bool seperator_is_key_plus_seq() override {
|
||||
bool seperator_is_key_plus_seq() override {
|
||||
return seperator_is_key_plus_seq_;
|
||||
}
|
||||
|
||||
@@ -306,13 +302,12 @@ class ShortenedIndexBuilder : public IndexBuilder {
|
||||
// data copy or small heap allocations for prefixes.
|
||||
class HashIndexBuilder : public IndexBuilder {
|
||||
public:
|
||||
explicit HashIndexBuilder(
|
||||
const InternalKeyComparator* comparator,
|
||||
const SliceTransform* hash_key_extractor,
|
||||
int index_block_restart_interval, int format_version,
|
||||
bool use_value_delta_encoding,
|
||||
BlockBasedTableOptions::IndexShorteningMode shortening_mode, size_t ts_sz,
|
||||
const bool persist_user_defined_timestamps)
|
||||
HashIndexBuilder(const InternalKeyComparator* comparator,
|
||||
const SliceTransform* hash_key_extractor,
|
||||
int index_block_restart_interval, int format_version,
|
||||
bool use_value_delta_encoding,
|
||||
BlockBasedTableOptions::IndexShorteningMode shortening_mode,
|
||||
size_t ts_sz, const bool persist_user_defined_timestamps)
|
||||
: IndexBuilder(comparator, ts_sz, persist_user_defined_timestamps),
|
||||
primary_index_builder_(comparator, index_block_restart_interval,
|
||||
format_version, use_value_delta_encoding,
|
||||
@@ -320,15 +315,15 @@ class HashIndexBuilder : public IndexBuilder {
|
||||
ts_sz, persist_user_defined_timestamps),
|
||||
hash_key_extractor_(hash_key_extractor) {}
|
||||
|
||||
virtual void AddIndexEntry(std::string* last_key_in_current_block,
|
||||
const Slice* first_key_in_next_block,
|
||||
const BlockHandle& block_handle) override {
|
||||
void AddIndexEntry(std::string* last_key_in_current_block,
|
||||
const Slice* first_key_in_next_block,
|
||||
const BlockHandle& block_handle) override {
|
||||
++current_restart_index_;
|
||||
primary_index_builder_.AddIndexEntry(last_key_in_current_block,
|
||||
first_key_in_next_block, block_handle);
|
||||
}
|
||||
|
||||
virtual void OnKeyAdded(const Slice& key) override {
|
||||
void OnKeyAdded(const Slice& key) override {
|
||||
auto key_prefix = hash_key_extractor_->Transform(key);
|
||||
bool is_first_entry = pending_block_num_ == 0;
|
||||
|
||||
@@ -355,9 +350,8 @@ class HashIndexBuilder : public IndexBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
virtual Status Finish(
|
||||
IndexBlocks* index_blocks,
|
||||
const BlockHandle& last_partition_block_handle) override {
|
||||
Status Finish(IndexBlocks* index_blocks,
|
||||
const BlockHandle& last_partition_block_handle) override {
|
||||
if (pending_block_num_ != 0) {
|
||||
FlushPendingPrefix();
|
||||
}
|
||||
@@ -370,12 +364,12 @@ class HashIndexBuilder : public IndexBuilder {
|
||||
return s;
|
||||
}
|
||||
|
||||
virtual size_t IndexSize() const override {
|
||||
size_t IndexSize() const override {
|
||||
return primary_index_builder_.IndexSize() + prefix_block_.size() +
|
||||
prefix_meta_block_.size();
|
||||
}
|
||||
|
||||
virtual bool seperator_is_key_plus_seq() override {
|
||||
bool seperator_is_key_plus_seq() override {
|
||||
return primary_index_builder_.seperator_is_key_plus_seq();
|
||||
}
|
||||
|
||||
@@ -421,27 +415,24 @@ class PartitionedIndexBuilder : public IndexBuilder {
|
||||
public:
|
||||
static PartitionedIndexBuilder* CreateIndexBuilder(
|
||||
const ROCKSDB_NAMESPACE::InternalKeyComparator* comparator,
|
||||
const bool use_value_delta_encoding,
|
||||
const BlockBasedTableOptions& table_opt, size_t ts_sz,
|
||||
const bool persist_user_defined_timestamps);
|
||||
bool use_value_delta_encoding, const BlockBasedTableOptions& table_opt,
|
||||
size_t ts_sz, bool persist_user_defined_timestamps);
|
||||
|
||||
explicit PartitionedIndexBuilder(const InternalKeyComparator* comparator,
|
||||
const BlockBasedTableOptions& table_opt,
|
||||
const bool use_value_delta_encoding,
|
||||
size_t ts_sz,
|
||||
const bool persist_user_defined_timestamps);
|
||||
PartitionedIndexBuilder(const InternalKeyComparator* comparator,
|
||||
const BlockBasedTableOptions& table_opt,
|
||||
bool use_value_delta_encoding, size_t ts_sz,
|
||||
bool persist_user_defined_timestamps);
|
||||
|
||||
virtual ~PartitionedIndexBuilder();
|
||||
~PartitionedIndexBuilder() override;
|
||||
|
||||
virtual void AddIndexEntry(std::string* last_key_in_current_block,
|
||||
const Slice* first_key_in_next_block,
|
||||
const BlockHandle& block_handle) override;
|
||||
void AddIndexEntry(std::string* last_key_in_current_block,
|
||||
const Slice* first_key_in_next_block,
|
||||
const BlockHandle& block_handle) override;
|
||||
|
||||
virtual Status Finish(
|
||||
IndexBlocks* index_blocks,
|
||||
const BlockHandle& last_partition_block_handle) override;
|
||||
Status Finish(IndexBlocks* index_blocks,
|
||||
const BlockHandle& last_partition_block_handle) override;
|
||||
|
||||
virtual size_t IndexSize() const override { return index_size_; }
|
||||
size_t IndexSize() const override { return index_size_; }
|
||||
size_t TopLevelIndexSize(uint64_t) const { return top_level_index_size_; }
|
||||
size_t NumPartitions() const;
|
||||
|
||||
@@ -460,11 +451,13 @@ class PartitionedIndexBuilder : public IndexBuilder {
|
||||
// cutting the next partition
|
||||
void RequestPartitionCut();
|
||||
|
||||
virtual bool seperator_is_key_plus_seq() override {
|
||||
bool seperator_is_key_plus_seq() override {
|
||||
return seperator_is_key_plus_seq_;
|
||||
}
|
||||
|
||||
bool get_use_value_delta_encoding() { return use_value_delta_encoding_; }
|
||||
bool get_use_value_delta_encoding() const {
|
||||
return use_value_delta_encoding_;
|
||||
}
|
||||
|
||||
private:
|
||||
// Set after ::Finish is called
|
||||
|
||||
@@ -85,7 +85,16 @@ class PlainTableBuilder : public TableBuilder {
|
||||
// Finish() call, returns the size of the final generated file.
|
||||
uint64_t FileSize() const override;
|
||||
|
||||
TableProperties GetTableProperties() const override { return properties_; }
|
||||
TableProperties GetTableProperties() const override {
|
||||
TableProperties ret = properties_;
|
||||
for (const auto& collector : table_properties_collectors_) {
|
||||
for (const auto& prop : collector->GetReadableProperties()) {
|
||||
ret.readable_properties.insert(prop);
|
||||
}
|
||||
collector->Finish(&ret.user_collected_properties).PermitUncheckedError();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool SaveIndexInFile() const { return store_index_in_file_; }
|
||||
|
||||
|
||||
@@ -196,6 +196,7 @@ Status SstFileDumper::NewTableReader(
|
||||
}
|
||||
|
||||
Status SstFileDumper::VerifyChecksum() {
|
||||
assert(read_options_.verify_checksums);
|
||||
// We could pass specific readahead setting into read options if needed.
|
||||
return table_reader_->VerifyChecksum(read_options_,
|
||||
TableReaderCaller::kSSTDumpTool);
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
EXIT_INVALID_ARGS=1
|
||||
EXIT_NOT_COMPACTION_TEST=2
|
||||
EXIT_UNKNOWN_JOB=3
|
||||
EXIT_INVALID_PATH=4
|
||||
|
||||
# Size Constants
|
||||
K=1024
|
||||
@@ -114,6 +115,11 @@ fi
|
||||
|
||||
job_id=${JOB_ID}
|
||||
|
||||
if [ ! -x ./db_bench ]; then
|
||||
echo "./db_bench not found. Please make sure it exists in the current directory."
|
||||
exit $EXIT_INVALID_PATH
|
||||
fi
|
||||
|
||||
# Make it easier to run only the compaction test. Getting valid data requires
|
||||
# a number of iterations and having an ability to run the test separately from
|
||||
# rest of the benchmarks helps.
|
||||
|
||||
@@ -125,7 +125,7 @@ EOF
|
||||
|
||||
# To check for DB forward compatibility with loading options (old version
|
||||
# reading data from new), as well as backward compatibility
|
||||
declare -a db_forward_with_options_refs=("6.27.fb" "6.28.fb" "6.29.fb" "7.0.fb" "7.1.fb" "7.2.fb" "7.3.fb" "7.4.fb" "7.5.fb" "7.6.fb" "7.7.fb" "7.8.fb" "7.9.fb" "7.10.fb" "8.0.fb" "8.1.fb" "8.2.fb" "8.3.fb")
|
||||
declare -a db_forward_with_options_refs=("6.27.fb" "6.28.fb" "6.29.fb" "7.0.fb" "7.1.fb" "7.2.fb" "7.3.fb" "7.4.fb" "7.5.fb" "7.6.fb" "7.7.fb" "7.8.fb" "7.9.fb" "7.10.fb" "8.0.fb" "8.1.fb" "8.2.fb" "8.3.fb" "8.4.fb")
|
||||
# To check for DB forward compatibility without loading options (in addition
|
||||
# to the "with loading options" set), as well as backward compatibility
|
||||
declare -a db_forward_no_options_refs=() # N/A at the moment
|
||||
|
||||
@@ -734,6 +734,7 @@ def gen_cmd(params, unknown_params):
|
||||
"stress_cmd",
|
||||
"test_tiered_storage",
|
||||
"cleanup_cmd",
|
||||
"skip_tmpdir_check"
|
||||
}
|
||||
and v is not None
|
||||
]
|
||||
@@ -1016,6 +1017,7 @@ def main():
|
||||
parser.add_argument("--stress_cmd")
|
||||
parser.add_argument("--test_tiered_storage", action="store_true")
|
||||
parser.add_argument("--cleanup_cmd")
|
||||
parser.add_argument("--skip_tmpdir_check", action="store_true")
|
||||
|
||||
all_params = dict(
|
||||
list(default_params.items())
|
||||
@@ -1042,13 +1044,13 @@ def main():
|
||||
args, unknown_args = parser.parse_known_args()
|
||||
|
||||
test_tmpdir = os.environ.get(_TEST_DIR_ENV_VAR)
|
||||
if test_tmpdir is not None:
|
||||
if test_tmpdir is not None and not args.skip_tmpdir_check:
|
||||
isdir = False
|
||||
try:
|
||||
isdir = os.path.isdir(test_tmpdir)
|
||||
if not isdir:
|
||||
print(
|
||||
"%s env var is set to a non-existent directory: %s"
|
||||
"ERROR: %s env var is set to a non-existent directory: %s. Update it to correct directory path."
|
||||
% (_TEST_DIR_ENV_VAR, test_tmpdir)
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
+8
-5
@@ -1310,7 +1310,8 @@ void DBLoaderCommand::DoCommand() {
|
||||
namespace {
|
||||
|
||||
void DumpManifestFile(Options options, std::string file, bool verbose, bool hex,
|
||||
bool json) {
|
||||
bool json,
|
||||
const std::vector<ColumnFamilyDescriptor>& cf_descs) {
|
||||
EnvOptions sopt;
|
||||
std::string dbname("dummy");
|
||||
std::shared_ptr<Cache> tc(NewLRUCache(options.max_open_files - 10,
|
||||
@@ -1326,7 +1327,7 @@ void DumpManifestFile(Options options, std::string file, bool verbose, bool hex,
|
||||
VersionSet versions(dbname, &immutable_db_options, sopt, tc.get(), &wb, &wc,
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
|
||||
/*db_id*/ "", /*db_session_id*/ "");
|
||||
Status s = versions.DumpManifest(options, file, verbose, hex, json);
|
||||
Status s = versions.DumpManifest(options, file, verbose, hex, json, cf_descs);
|
||||
if (!s.ok()) {
|
||||
fprintf(stderr, "Error in processing file %s %s\n", file.c_str(),
|
||||
s.ToString().c_str());
|
||||
@@ -1438,7 +1439,8 @@ void ManifestDumpCommand::DoCommand() {
|
||||
fprintf(stdout, "Processing Manifest file %s\n", manifestfile.c_str());
|
||||
}
|
||||
|
||||
DumpManifestFile(options_, manifestfile, verbose_, is_key_hex_, json_);
|
||||
DumpManifestFile(options_, manifestfile, verbose_, is_key_hex_, json_,
|
||||
column_families_);
|
||||
|
||||
if (verbose_) {
|
||||
fprintf(stdout, "Processing Manifest file %s done\n", manifestfile.c_str());
|
||||
@@ -2046,7 +2048,7 @@ void DBDumperCommand::DoCommand() {
|
||||
break;
|
||||
case kDescriptorFile:
|
||||
DumpManifestFile(options_, path_, /* verbose_ */ false, is_key_hex_,
|
||||
/* json_ */ false);
|
||||
/* json_ */ false, column_families_);
|
||||
break;
|
||||
case kBlobFile:
|
||||
DumpBlobFile(path_, is_key_hex_, is_value_hex_,
|
||||
@@ -3706,7 +3708,8 @@ void DBFileDumperCommand::DoCommand() {
|
||||
manifest_filepath = NormalizePath(manifest_filepath);
|
||||
|
||||
std::cout << manifest_filepath << std::endl;
|
||||
DumpManifestFile(options_, manifest_filepath, false, false, false);
|
||||
DumpManifestFile(options_, manifest_filepath, false, false, false,
|
||||
column_families_);
|
||||
std::cout << std::endl;
|
||||
|
||||
std::vector<ColumnFamilyMetaData> column_families;
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
# Exit Codes
|
||||
EXIT_INVALID_ARGS=1
|
||||
EXIT_INVALID_PATH=2
|
||||
|
||||
# Size constants
|
||||
K=1024
|
||||
@@ -74,6 +75,11 @@ if [ $# -ge 1 ]; then
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ ! -f tools/benchmark.sh ]; then
|
||||
echo "tools/benchmark.sh not found"
|
||||
exit $EXIT_INVALID_PATH
|
||||
fi
|
||||
|
||||
# shellcheck disable=SC2153
|
||||
if [ -z "$DB_DIR" ]; then
|
||||
echo "DB_DIR is not defined"
|
||||
|
||||
@@ -419,6 +419,10 @@ int SSTDumpTool::Run(int argc, char const* const* argv, Options options) {
|
||||
filename = std::string(dir_or_file) + "/" + filename;
|
||||
}
|
||||
|
||||
if (command == "verify") {
|
||||
verify_checksum = true;
|
||||
}
|
||||
|
||||
ROCKSDB_NAMESPACE::SstFileDumper dumper(
|
||||
options, filename, Temperature::kUnknown, readahead_size,
|
||||
verify_checksum, output_hex, decode_blob_index);
|
||||
|
||||
@@ -31,7 +31,7 @@ awk '/#define ROCKSDB_MAJOR/ { major = $3 }
|
||||
/#define ROCKSDB_MINOR/ { minor = $3 }
|
||||
/#define ROCKSDB_PATCH/ { patch = $3 }
|
||||
END { printf "## " major "." minor "." patch }' < include/rocksdb/version.h >> HISTORY.new
|
||||
echo " (`date +%x`)" >> HISTORY.new
|
||||
echo " (`git log -n1 --date=format:"%m/%d/%Y" --format="%ad"`)" >> HISTORY.new
|
||||
|
||||
function process_file () {
|
||||
# use awk to correct extra or missing newlines, missing '* ' on first line
|
||||
|
||||
+7
-1
@@ -1117,7 +1117,13 @@ static inline Function Choose_Extend() {
|
||||
}
|
||||
#elif defined(__SSE4_2__) && defined(__PCLMUL__) && !defined NO_THREEWAY_CRC32C
|
||||
// NOTE: runtime detection no longer supported on x86
|
||||
(void)ExtendImpl<DefaultCRC32>; // suppress unused warning
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(disable: 4551)
|
||||
#endif
|
||||
(void)ExtendImpl<DefaultCRC32>; // suppress unused warning
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(default: 4551)
|
||||
#endif
|
||||
return crc32c_3way;
|
||||
#else
|
||||
return ExtendImpl<DefaultCRC32>;
|
||||
|
||||
@@ -11,6 +11,9 @@
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#endif
|
||||
#ifdef __OpenBSD__
|
||||
#include <sys/wait.h>
|
||||
#endif
|
||||
#include <vector>
|
||||
|
||||
#include "test_util/testharness.h"
|
||||
|
||||
+2
-2
@@ -36,7 +36,7 @@ RecoveryType GetRecoveryType(const size_t running_ts_sz,
|
||||
return RecoveryType::kPadTimestamp;
|
||||
}
|
||||
|
||||
if (running_ts_sz != recorded_ts_sz.value()) {
|
||||
if (running_ts_sz != *recorded_ts_sz) {
|
||||
return RecoveryType::kUnrecoverable;
|
||||
}
|
||||
|
||||
@@ -214,7 +214,7 @@ Status TimestampRecoveryHandler::ReconcileTimestampDiscrepancy(
|
||||
break;
|
||||
case RecoveryType::kStripTimestamp:
|
||||
assert(record_ts_sz.has_value());
|
||||
*new_key = StripTimestampFromUserKey(key, record_ts_sz.value());
|
||||
*new_key = StripTimestampFromUserKey(key, *record_ts_sz);
|
||||
new_batch_diff_from_orig_batch_ = true;
|
||||
break;
|
||||
case RecoveryType::kPadTimestamp:
|
||||
|
||||
Reference in new issue
Block a user