cachelib/navy/block_cache/Index.cpp (168 lines of code) (raw):
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* 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.
*/
#include "cachelib/navy/block_cache/Index.h"
#include <folly/Format.h>
#include "cachelib/navy/serialization/Serialization.h"
namespace facebook {
namespace cachelib {
namespace navy {
constexpr uint32_t Index::kNumBuckets; // Link error otherwise
namespace {
// increase val if no overflow, otherwise do nothing
uint8_t safeInc(uint8_t val) {
if (val < std::numeric_limits<uint8_t>::max()) {
return val + 1;
}
return val;
}
} // namespace
void Index::setHits(uint64_t key, uint8_t currentHits, uint8_t totalHits) {
auto& map = getMap(key);
auto lock = std::lock_guard{getMutex(key)};
auto it = map.find(subkey(key));
if (it != map.end()) {
it.value().currentHits = currentHits;
it.value().totalHits = totalHits;
}
}
Index::LookupResult Index::lookup(uint64_t key) {
LookupResult lr;
auto& map = getMap(key);
auto lock = std::lock_guard{getMutex(key)};
auto it = map.find(subkey(key));
if (it != map.end()) {
lr.found_ = true;
lr.record_ = it->second;
it.value().totalHits = safeInc(lr.record_.totalHits);
it.value().currentHits = safeInc(lr.record_.currentHits);
}
return lr;
}
Index::LookupResult Index::peek(uint64_t key) const {
LookupResult lr;
const auto& map = getMap(key);
auto lock = std::shared_lock{getMutex(key)};
auto it = map.find(subkey(key));
if (it != map.end()) {
lr.found_ = true;
lr.record_ = it->second;
}
return lr;
}
Index::LookupResult Index::insert(uint64_t key,
uint32_t address,
uint16_t sizeHint) {
LookupResult lr;
auto& map = getMap(key);
auto lock = std::lock_guard{getMutex(key)};
auto it = map.find(subkey(key));
if (it != map.end()) {
lr.found_ = true;
lr.record_ = it->second;
trackRemove(it->second.totalHits);
// tsl::sparse_map's `it->second` is immutable, while it.value() is mutable
it.value().address = address;
it.value().currentHits = 0;
it.value().totalHits = 0;
it.value().sizeHint = sizeHint;
} else {
map.try_emplace(key, address, sizeHint);
}
return lr;
}
bool Index::replaceIfMatch(uint64_t key,
uint32_t newAddress,
uint32_t oldAddress) {
auto& map = getMap(key);
auto lock = std::lock_guard{getMutex(key)};
auto it = map.find(subkey(key));
if (it != map.end() && it->second.address == oldAddress) {
// tsl::sparse_map's `it->second` is immutable, while it.value() is mutable
it.value().address = newAddress;
it.value().currentHits = 0;
return true;
}
return false;
}
void Index::trackRemove(uint8_t totalHits) {
hitsEstimator_.trackValue(totalHits);
if (totalHits == 0) {
unAccessedItems_.inc();
}
}
Index::LookupResult Index::remove(uint64_t key) {
LookupResult lr;
auto& map = getMap(key);
auto lock = std::lock_guard{getMutex(key)};
auto it = map.find(subkey(key));
if (it != map.end()) {
lr.found_ = true;
lr.record_ = it->second;
trackRemove(it->second.totalHits);
map.erase(it);
}
return lr;
}
bool Index::removeIfMatch(uint64_t key, uint32_t address) {
auto& map = getMap(key);
auto lock = std::lock_guard{getMutex(key)};
auto it = map.find(subkey(key));
if (it != map.end() && it->second.address == address) {
trackRemove(it->second.totalHits);
map.erase(it);
return true;
}
return false;
}
void Index::reset() {
for (uint32_t i = 0; i < kNumBuckets; i++) {
auto lock = std::lock_guard{getMutexOfBucket(i)};
buckets_[i].clear();
}
}
size_t Index::computeSize() const {
size_t size = 0;
for (uint32_t i = 0; i < kNumBuckets; i++) {
auto lock = std::lock_guard{getMutexOfBucket(i)};
size += buckets_[i].size();
}
return size;
}
void Index::persist(RecordWriter& rw) const {
serialization::IndexBucket bucket;
for (uint32_t i = 0; i < kNumBuckets; i++) {
*bucket.bucketId_ref() = i;
// Convert index entries to thrift objects
for (const auto& [key, record] : buckets_[i]) {
serialization::IndexEntry entry;
entry.key_ref() = key;
entry.address_ref() = record.address;
entry.sizeHint_ref() = record.sizeHint;
entry.totalHits_ref() = record.totalHits;
entry.currentHits_ref() = record.currentHits;
bucket.entries_ref()->push_back(entry);
}
// Serialize bucket then clear contents to reuse memory.
serializeProto(bucket, rw);
bucket.entries_ref()->clear();
}
}
void Index::recover(RecordReader& rr) {
for (uint32_t i = 0; i < kNumBuckets; i++) {
auto bucket = deserializeProto<serialization::IndexBucket>(rr);
uint32_t id = *bucket.bucketId_ref();
if (id >= kNumBuckets) {
throw std::invalid_argument{
folly::sformat("Invalid bucket id. Max buckets: {}, bucket id: {}",
kNumBuckets,
id)};
}
for (auto& entry : *bucket.entries_ref()) {
buckets_[id].try_emplace(*entry.key_ref(),
*entry.address_ref(),
*entry.sizeHint_ref(),
*entry.totalHits_ref(),
*entry.currentHits_ref());
}
}
}
void Index::getCounters(const CounterVisitor& visitor) const {
hitsEstimator_.visitQuantileEstimator(visitor, "navy_bc_item_hits");
visitor("navy_bc_item_removed_with_no_access", unAccessedItems_.get());
}
} // namespace navy
} // namespace cachelib
} // namespace facebook