foundationdb/fdbclient/SnapshotCache.h

401 lines
13 KiB
C++

/*
* SnapshotCache.h
*
* This source file is part of the FoundationDB open source project
*
* Copyright 2013-2022 Apple Inc. and the FoundationDB project authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef FDBCLIENT_SNAPSHOTCACHE_H
#define FDBCLIENT_SNAPSHOTCACHE_H
#pragma once
#include "fdbclient/FDBTypes.h"
#include "fdbclient/NativeAPI.actor.h"
#include "fdbclient/SystemData.h"
#include "flow/IndexedSet.h"
struct ExtStringRef {
ExtStringRef() : extra_zero_bytes(0) {}
ExtStringRef(StringRef const& s, int extra_zero_bytes = 0) : base(s), extra_zero_bytes(extra_zero_bytes) {}
Standalone<StringRef> toStandaloneStringRef() const {
auto s = makeString(size());
if (base.size() > 0) {
memcpy(mutateString(s), base.begin(), base.size());
}
memset(mutateString(s) + base.size(), 0, extra_zero_bytes);
return s;
};
StringRef toArenaOrRef(Arena& a) const {
if (extra_zero_bytes) {
StringRef dest = StringRef(new (a) uint8_t[size()], size());
if (base.size() > 0) {
memcpy(mutateString(dest), base.begin(), base.size());
}
memset(mutateString(dest) + base.size(), 0, extra_zero_bytes);
return dest;
} else
return base;
}
StringRef assertRef() const {
ASSERT(extra_zero_bytes == 0);
return base;
}
StringRef toArena(Arena& a) const {
if (extra_zero_bytes) {
StringRef dest = StringRef(new (a) uint8_t[size()], size());
if (base.size() > 0) {
memcpy(mutateString(dest), base.begin(), base.size());
}
memset(mutateString(dest) + base.size(), 0, extra_zero_bytes);
return dest;
} else
return StringRef(a, base);
}
int size() const { return base.size() + extra_zero_bytes; }
int compare(ExtStringRef const& rhs) const {
int cbl = std::min(base.size(), rhs.base.size());
if (cbl > 0) {
int c = memcmp(base.begin(), rhs.base.begin(), cbl);
if (c != 0)
return c;
}
for (int i = cbl; i < base.size(); i++)
if (base[i])
return 1;
for (int i = cbl; i < rhs.base.size(); i++)
if (rhs.base[i])
return -1;
return ::compare(size(), rhs.size());
}
bool startsWith(const ExtStringRef& s) const {
if (size() < s.size())
return false;
int cbl = std::min(base.size(), s.base.size());
for (int i = cbl; i < std::min(s.size(), base.size()); i++)
if (base[i])
return false;
for (int i = cbl; i < s.base.size(); i++)
if (s.base[i])
return false;
return !memcmp(base.begin(), s.base.begin(), cbl);
}
bool isKeyAfter(ExtStringRef const& s) const {
if (size() != s.size() + 1)
return false;
if (extra_zero_bytes == 0 && base[base.size() - 1] != 0)
return false;
return startsWith(s);
}
ExtStringRef keyAfter() const { return ExtStringRef(base, extra_zero_bytes + 1); }
private:
friend struct Traceable<ExtStringRef>;
StringRef base;
int extra_zero_bytes;
};
inline bool operator==(const ExtStringRef& lhs, const ExtStringRef& rhs) {
return lhs.size() == rhs.size() && !lhs.compare(rhs);
}
inline bool operator!=(const ExtStringRef& lhs, const ExtStringRef& rhs) {
return !(lhs == rhs);
}
inline bool operator<(const ExtStringRef& lhs, const ExtStringRef& rhs) {
return lhs.compare(rhs) < 0;
}
inline bool operator>(const ExtStringRef& lhs, const ExtStringRef& rhs) {
return lhs.compare(rhs) > 0;
}
inline bool operator<=(const ExtStringRef& lhs, const ExtStringRef& rhs) {
return lhs.compare(rhs) <= 0;
}
inline bool operator>=(const ExtStringRef& lhs, const ExtStringRef& rhs) {
return lhs.compare(rhs) >= 0;
}
template <>
struct Traceable<ExtStringRef> : std::true_type {
static std::string toString(const ExtStringRef str) {
std::string result;
result.reserve(str.size());
std::copy(str.base.begin(), str.base.end(), std::back_inserter(result));
for (int i = 0; i < str.extra_zero_bytes; ++i) {
result.push_back('\0');
}
return Traceable<std::string>::toString(result);
}
};
class SnapshotCache {
private:
struct Entry {
// An entry represents a range of keys which are known. The keys which are not present in `values` are
// implicitly empty.
KeyRef beginKey;
ExtStringRef endKey;
VectorRef<KeyValueRef> values;
Entry(KeyRef const& beginKey, ExtStringRef const& endKey, VectorRef<KeyValueRef> const& values)
: beginKey(beginKey), endKey(endKey), values(values) {}
Entry(KeyValueRef const& kv, Arena& arena) : beginKey(kv.key), endKey(kv.key, 1) {
values.push_back(arena, kv);
}
int compare(Entry const& r) const { return ::compare(beginKey, r.beginKey); }
bool operator<(Entry const& r) const { return beginKey < r.beginKey; }
int segments() const { return 2 * (values.size() + 1); }
};
friend class ReadYourWritesTransaction;
Arena* arena;
IndexedSet<Entry, NoMetric> entries;
public:
struct iterator {
// Iterates over three types of segments: individual key/value pairs, empty ranges, and ranges which are not
// known. Every key will belong to exactly one segment. The first segment begins at "" and the last segment
// ends at \xff\xff.
// Note that an uncached range stops at the next individual key that is known, even though we might want to read
// through that key. In RYWIterator, it might also stop at a dependent write which is not known at all!
iterator(SnapshotCache* cache) : parent(cache), it(cache->entries.begin()), offset(0) {
//++*this; // gives begin
}
iterator(SnapshotCache* cache, class WriteMap* writes)
: parent(cache), it(cache->entries.begin()), offset(0) {
} // for RYW to use the same constructor for snapshot cache and RYWIterator
enum SEGMENT_TYPE { UNKNOWN_RANGE, EMPTY_RANGE, KV };
SEGMENT_TYPE type() const {
if (!offset)
return UNKNOWN_RANGE;
if (offset & 1)
return EMPTY_RANGE;
return KV;
}
bool is_kv() const { return type() == KV; }
bool is_unknown_range() const { return type() == UNKNOWN_RANGE; }
bool is_empty_range() const { return type() == EMPTY_RANGE; }
bool is_dependent() const { return false; }
bool is_unreadable() const { return false; }
void bypassUnreadableProtection() {}
ExtStringRef beginKey() const {
if (offset == 0) {
auto prev = it;
prev.decrementNonEnd();
return prev->endKey;
} else if (offset == 1)
return it->beginKey;
else
return ExtStringRef(it->values[(offset - 2) >> 1].key, offset & 1);
}
ExtStringRef endKey() const {
if (offset == 0)
return it->beginKey;
else if (offset == it->segments() - 1)
return it->endKey;
else
return ExtStringRef(it->values[(offset - 1) >> 1].key, 1 - (offset & 1));
}
const KeyValueRef* kv(Arena& arena) const { // only if is_kv()
return &it->values[(offset - 2) >> 1];
}
iterator& operator++() {
ExtStringRef originalEnd = endKey();
do {
offset++;
if (offset == it->segments()) {
offset = 0;
++it;
}
} while (endKey() == originalEnd); // TODO: pointer only comparison; maintain equality of pointers to keys
// around degenerate segments
return *this;
}
iterator& operator--() {
ExtStringRef originalBegin = beginKey();
do {
offset--;
if (offset < 0) {
it.decrementNonEnd();
offset = it->segments() - 1;
}
} while (beginKey() == originalBegin);
return *this;
}
bool operator==(const iterator& r) const { return it == r.it && offset == r.offset; }
bool operator!=(const iterator& r) const { return !(*this == r); }
void skip(
KeyRef key) { // Changes *this to the segment containing key (so that beginKey()<=key && key < endKey())
if (key == allKeys.end) {
it = parent->entries.lastItem();
offset = 1;
return;
}
it = parent->entries.lastLessOrEqual(Entry(key, key, VectorRef<KeyValueRef>())); // TODO: finger query?
if (key >= it->endKey) {
offset = 0;
++it;
} else {
int idx = std::lower_bound(it->values.begin(), it->values.end(), key, KeyValueRef::OrderByKey()) -
it->values.begin();
offset = idx * 2 + 1 + (idx < it->values.size() && it->values[idx].key == key);
}
}
void skipContiguous(ExtStringRef key) { // Changes *this to be the last iterator i | the elements e of array
// [&*this, &*i] all have e->key < key
offset =
2 *
(std::lower_bound(it->values.begin() + offset / 2, it->values.end(), key, KeyValueRef::OrderByKey()) -
it->values.begin());
}
void skipContiguousBack(ExtStringRef key) { // Changes *this to be the first iterator i | the elements e of
// array [&*i, &*this] all have e->key >= key
offset = 2 * (std::lower_bound(
it->values.begin(), it->values.begin() + offset / 2 - 1, key, KeyValueRef::OrderByKey()) -
it->values.begin()) +
2;
}
void _nextUnknown() { // For internal use only - can return a degenerate segment
++it;
offset = 0;
}
void _prevUnknown() { // For internal use only - can return a degenerate segment
offset = 0;
}
void dbg() {}
private:
friend class SnapshotCache;
SnapshotCache* parent;
IndexedSet<Entry, NoMetric>::iterator it;
int offset; // 0 <= offset < it->segments()
};
explicit SnapshotCache(Arena* arena) : arena(arena) {
// Degenerate entries at the beginning and end reduce edge cases
entries.insert(Entry(allKeys.begin, allKeys.begin, VectorRef<KeyValueRef>()), NoMetric(), true);
entries.insert(Entry(allKeys.end, afterAllKeys, VectorRef<KeyValueRef>()), NoMetric(), true);
}
// Visual Studio refuses to generate these, apparently despite the standard
SnapshotCache(SnapshotCache&& r) noexcept : arena(r.arena), entries(std::move(r.entries)) {}
SnapshotCache& operator=(SnapshotCache&& r) noexcept {
entries = std::move(r.entries);
arena = r.arena;
return *this;
}
bool empty() const {
// Returns true iff anything is known about the contents of the snapshot
for (auto i = entries.begin(); i != entries.end(); ++i)
if (i->beginKey != i->endKey && i->beginKey < allKeys.end)
return false;
return true;
}
bool insert(KeyRef key, Optional<ValueRef> value) {
// Asserts that, in the snapshot, the given key has the given value (or is not present, if !value.present())
iterator it(this);
it.skip(key);
if (it.is_unknown_range()) {
if (value.present())
entries.insert(Entry(KeyValueRef(key, value.get()), *arena), NoMetric(), true);
else
entries.insert(Entry(key, ExtStringRef(key, 1), VectorRef<KeyValueRef>()), NoMetric(), true);
return true;
}
return false;
}
bool insert(KeyRangeRef keys, VectorRef<KeyValueRef> values) {
// Asserts that, in the snapshot, the given ranges of keys contains (only) the given key/value pairs
// The returned iterator points to the first key in the range, or after the range if !values.size()
if (keys.empty())
return false;
iterator itb(this);
itb.skip(keys.begin);
iterator ite = itb;
ite.skip(keys.end);
StringRef begin = keys.begin;
if (!itb.is_unknown_range() && itb.it->beginKey != keys.begin) {
begin = itb.it->endKey.toArenaOrRef(*arena);
auto i = std::lower_bound(values.begin(), values.end(), begin, KeyValueRef::OrderByKey());
values = VectorRef<KeyValueRef>(i, values.end() - i);
itb._nextUnknown();
}
ExtStringRef end = keys.end;
if (!ite.is_unknown_range()) {
ite._prevUnknown();
end = ite.endKey();
values.resize(*arena,
std::lower_bound(values.begin(), values.end(), end, KeyValueRef::OrderByKey()) -
values.begin());
}
if (begin < end) {
bool addBegin = begin != allKeys.begin && itb.it->beginKey == allKeys.begin;
entries.erase(itb.it, ite.it);
entries.insert(Entry(begin, end, values), NoMetric(), true);
if (addBegin)
entries.insert(Entry(allKeys.begin, allKeys.begin, VectorRef<KeyValueRef>()), NoMetric(), true);
return true;
}
return false;
}
void dump() {
for (auto it = entries.begin(); it != entries.end(); ++it) {
TraceEvent("CacheDump")
.detail("Begin", it->beginKey)
.detail("End", it->endKey.toStandaloneStringRef())
.detail("Values", it->values);
}
}
void promise(iterator const& segment, KeyRangeRef keys, Future<Void> onReady);
// Asserts that the caller is reading the contents of the given range of keys and will later call insert(keys, ?)
// and then set the given onReady future.
// ? Also asserts that segment.uncached_range().begin() <= keys.begin && segment.uncached_range() >= keys.end
// segment is not invalidated
};
#endif