Skip to content

Commit 941cd88

Browse files
byahn0996facebook-github-bot
authored andcommitted
Simple implementation of FixedSizeIndex
Summary: The first version of FixedSizeIndex implementation. It's mostly based on Anton's 5B prototype/hack code. There will be many improvement points here and there, but will be good to have a pure minimum version as a starting point to make it work and evaluate with the different options easily. Reviewed By: alikhtarov Differential Revision: D73440703 fbshipit-source-id: 6ef189d8df8bc59bf3aa21fa13d2abcdc2fb4ebb
1 parent 2f4b9f7 commit 941cd88

5 files changed

Lines changed: 785 additions & 1 deletion

File tree

Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#include "cachelib/navy/block_cache/FixedSizeIndex.h"
18+
19+
#include <folly/logging/xlog.h>
20+
21+
#include "cachelib/navy/serialization/Serialization.h"
22+
23+
namespace facebook {
24+
namespace cachelib {
25+
namespace navy {
26+
27+
void FixedSizeIndex::initialize() {
28+
XDCHECK(numChunks_ != 0 && numBucketsPerChunkPower_ != 0 &&
29+
numBucketsPerMutex_ != 0);
30+
31+
XDCHECK(numBucketsPerChunkPower_ <= 63);
32+
bucketsPerChunk_ = (1ull << numBucketsPerChunkPower_);
33+
totalBuckets_ = numChunks_ * bucketsPerChunk_;
34+
35+
XDCHECK(numBucketsPerMutex_ <= totalBuckets_);
36+
totalMutexes_ = (totalBuckets_ - 1) / numBucketsPerMutex_ + 1;
37+
38+
ht_ = std::make_unique<PackedItemRecord[]>(totalBuckets_);
39+
mutex_ = std::make_unique<SharedMutex[]>(totalMutexes_);
40+
sizeForMutex_ = std::make_unique<size_t[]>(totalMutexes_);
41+
}
42+
43+
Index::LookupResult FixedSizeIndex::lookup(uint64_t key) {
44+
// TODO 1: We are holding a exclusive lock here because we're updating hit
45+
// count info (currentHits here). Need to re-evaluate if there's benefit by
46+
// using shared lock and loosly managing hit count update. (Same with in
47+
// SparseMapIndex)
48+
//
49+
// TODO 2: couldExist() currently calls Index::lookup(). Need to re-evaluate
50+
// if we should use peek() instead so that it could avoid exclusive lock and
51+
// also not sure if bumping up hit count with couldExist() makes sense.
52+
ExclusiveLockedBucket elb{key, *this};
53+
54+
if (elb.recordRef().isValid()) {
55+
return LookupResult(true,
56+
ItemRecord(elb.recordRef().address,
57+
elb.recordRef().getSizeHint(),
58+
0, /* totalHits */
59+
elb.recordRef().bumpCurHits()));
60+
}
61+
62+
return {};
63+
}
64+
65+
Index::LookupResult FixedSizeIndex::peek(uint64_t key) const {
66+
SharedLockedBucket slb{key, *this};
67+
68+
if (slb.recordRef().isValid()) {
69+
return LookupResult(true,
70+
ItemRecord(slb.recordRef().address,
71+
slb.recordRef().getSizeHint(),
72+
0, /* totalHits */
73+
slb.recordRef().info.curHits));
74+
}
75+
76+
return {};
77+
}
78+
79+
Index::LookupResult FixedSizeIndex::insert(uint64_t key,
80+
uint32_t address,
81+
uint16_t sizeHint) {
82+
LookupResult lr;
83+
ExclusiveLockedBucket elb{key, *this};
84+
85+
if (elb.recordRef().isValid()) {
86+
lr = LookupResult(true,
87+
ItemRecord(elb.recordRef().address,
88+
elb.recordRef().getSizeHint(),
89+
0, /* totalHits */
90+
elb.recordRef().info.curHits));
91+
} else {
92+
++elb.sizeRef();
93+
}
94+
elb.recordRef() = PackedItemRecord{address, sizeHint, /* currentHits */ 0};
95+
96+
return lr;
97+
}
98+
99+
bool FixedSizeIndex::replaceIfMatch(uint64_t key,
100+
uint32_t newAddress,
101+
uint32_t oldAddress) {
102+
ExclusiveLockedBucket elb{key, *this};
103+
104+
if (elb.recordRef().address == oldAddress) {
105+
elb.recordRef().address = newAddress;
106+
elb.recordRef().info.curHits = 0;
107+
return true;
108+
}
109+
return false;
110+
}
111+
112+
Index::LookupResult FixedSizeIndex::remove(uint64_t key) {
113+
ExclusiveLockedBucket elb{key, *this};
114+
115+
if (elb.recordRef().isValid()) {
116+
LookupResult lr{true, ItemRecord(elb.recordRef().address,
117+
elb.recordRef().getSizeHint(),
118+
0, /* totalHits */
119+
elb.recordRef().info.curHits)};
120+
121+
XDCHECK(elb.sizeRef() > 0);
122+
--elb.sizeRef();
123+
elb.recordRef() = PackedItemRecord{};
124+
return lr;
125+
}
126+
127+
elb.recordRef() = PackedItemRecord{};
128+
return {};
129+
}
130+
131+
bool FixedSizeIndex::removeIfMatch(uint64_t key, uint32_t address) {
132+
ExclusiveLockedBucket elb{key, *this};
133+
134+
if (elb.recordRef().address == address) {
135+
elb.recordRef() = PackedItemRecord{};
136+
137+
XDCHECK(elb.sizeRef() > 0);
138+
--elb.sizeRef();
139+
140+
return true;
141+
}
142+
return false;
143+
}
144+
145+
void FixedSizeIndex::reset() {
146+
uint64_t bucketId = 0;
147+
for (uint32_t i = 0; i < totalMutexes_; i++) {
148+
auto lock = std::lock_guard{mutex_[i]};
149+
for (uint64_t j = 0; j < numBucketsPerMutex_; ++j) {
150+
ht_[bucketId++] = PackedItemRecord{};
151+
}
152+
sizeForMutex_[i] = 0;
153+
}
154+
}
155+
156+
size_t FixedSizeIndex::computeSize() const {
157+
size_t size = 0;
158+
for (uint32_t i = 0; i < totalMutexes_; i++) {
159+
auto lock = std::shared_lock{mutex_[i]};
160+
size += sizeForMutex_[i];
161+
}
162+
163+
return size;
164+
}
165+
166+
Index::MemFootprintRange FixedSizeIndex::computeMemFootprintRange() const {
167+
Index::MemFootprintRange range;
168+
169+
size_t memUsage = 0;
170+
memUsage += totalBuckets_ * sizeof(PackedItemRecord);
171+
memUsage += totalMutexes_ * sizeof(SharedMutex);
172+
memUsage += totalMutexes_ * sizeof(size_t);
173+
174+
range.maxUsedBytes = memUsage;
175+
range.minUsedBytes = memUsage;
176+
return range;
177+
}
178+
179+
void FixedSizeIndex::persist(RecordWriter& rw) const {
180+
// TODO: need to revisit persist and recover
181+
// : We already know that we don't handle well when we write more than
182+
// pre-configured metadata size by serializing too many items, and there are
183+
// ideas for improvements for that.
184+
// For now, this will follow the same logic with the exisiting SparseMapIndex
185+
XLOGF(INFO, "Persisting BlockCache hashtable: {} buckets", totalBuckets_);
186+
for (uint64_t i = 0; i < totalBuckets_; ++i) {
187+
serialization::IndexEntry entry;
188+
entry.key() = i;
189+
entry.address() = ht_[i].address;
190+
entry.sizeHint() = ht_[i].getSizeHint();
191+
entry.currentHits() = (uint8_t)(ht_[i].info.curHits);
192+
193+
serializeProto(entry, rw);
194+
}
195+
XLOG(INFO) << "Finished persisting BlockCache hashtable";
196+
}
197+
198+
void FixedSizeIndex::recover(RecordReader& rr) {
199+
// TODO need to revisit persist and recover. See the comment in persist().
200+
XLOGF(INFO, "Recovering BlockCache hashtable: {} buckets", totalBuckets_);
201+
for (uint64_t i = 0; i < totalBuckets_; ++i) {
202+
auto entry = deserializeProto<serialization::IndexEntry>(rr);
203+
if (static_cast<uint64_t>(*entry.key()) >= totalBuckets_) {
204+
continue;
205+
}
206+
207+
if (PackedItemRecord::isValidAddress(*entry.address())) {
208+
// valid entry
209+
ht_[*entry.key()] =
210+
PackedItemRecord{static_cast<uint32_t>(*entry.address()),
211+
static_cast<uint16_t>(*entry.sizeHint()),
212+
static_cast<uint8_t>(*entry.currentHits())};
213+
++sizeForMutex_[*entry.key() / numBucketsPerMutex_];
214+
} else {
215+
ht_[*entry.key()] = PackedItemRecord{};
216+
}
217+
}
218+
XLOG(INFO) << "Finished recovering BlockCache hashtable";
219+
}
220+
221+
void FixedSizeIndex::getCounters(const CounterVisitor&) const {
222+
// TODO: nothing to add for now
223+
return;
224+
}
225+
226+
void FixedSizeIndex::setHits(uint64_t key,
227+
uint8_t currentHits,
228+
uint8_t totalHits) {
229+
ExclusiveLockedBucket elb{key, *this};
230+
231+
if (elb.recordRef().isValid()) {
232+
elb.recordRef().info.curHits =
233+
PackedItemRecord::truncateCurHits(currentHits);
234+
XLOGF(INFO,
235+
"setHits() for {}. totalHits {} was discarded in FixedSizeIndex",
236+
key,
237+
totalHits);
238+
}
239+
}
240+
241+
} // namespace navy
242+
} // namespace cachelib
243+
} // namespace facebook

0 commit comments

Comments
 (0)