Skip to content

Commit 95fc453

Browse files
committed
Background movers implementation
This is the implementation logic for the background eviction (and promotion when multi-tier is enabled). The main parameters for the background workers are the number of threads, `backgroundMoverThreads` and the batch size used `backgroundEvictionBatch`, which configures the number of items to evict in batch (while holding the container lock), and `backgroundTargetFree` which sets target free percentage of each class. The background workers will work to keep that percentage of space free. The main result is that SET (allocate) latencies are significantly reduced - kvcache workload with 40GB DRAM, ampFactor set to 200 via the trace replayer. Throughput set at 1.2M ops/sec. | Percentile | % Improvement | |------------|---------------| | 0.50000 | 70.4 | | 0.90000 | 23.0 | | 0.99000 | 12.2 | | 0.99900 | 89.7 | | 0.99990 | 24.9 | | 0.99999 | 0.6 | The GET (find) latencies are unaffected by the background workers as long as the batch size remains reasonably small (10 in our tests).
1 parent efa4506 commit 95fc453

25 files changed

+1071
-620
lines changed

cachelib/allocator/BackgroundMover.h

+156-70
Original file line numberDiff line numberDiff line change
@@ -16,51 +16,63 @@
1616

1717
#pragma once
1818

19-
#include "cachelib/allocator/BackgroundMoverStrategy.h"
19+
#include "cachelib/allocator/Cache.h"
2020
#include "cachelib/allocator/CacheStats.h"
21-
#include "cachelib/common/AtomicCounter.h"
2221
#include "cachelib/common/PeriodicWorker.h"
2322

2423
namespace facebook::cachelib {
2524
// wrapper that exposes the private APIs of CacheType that are specifically
2625
// needed for the cache api
2726
template <typename C>
2827
struct BackgroundMoverAPIWrapper {
29-
static size_t traverseAndEvictItems(C& cache,
30-
unsigned int pid,
31-
unsigned int cid,
32-
size_t batch) {
33-
return cache.traverseAndEvictItems(pid, cid, batch);
28+
// traverse the cache and move items from one tier to another
29+
// @param cache the cache interface
30+
// @param pid the pool id to traverse
31+
// @param cid the class id to traverse
32+
// @param evictionBatch number of items to evict in one go
33+
// @param promotionBatch number of items to promote in one go
34+
// @return pair of number of items evicted and promoted
35+
static std::pair<size_t, size_t> traverseAndMoveItems(C& cache,
36+
PoolId pid,
37+
ClassId cid,
38+
size_t evictionBatch,
39+
size_t promotionBatch) {
40+
return cache.traverseAndMoveItems(pid, cid, evictionBatch, promotionBatch);
3441
}
35-
36-
static size_t traverseAndPromoteItems(C& cache,
37-
unsigned int pid,
38-
unsigned int cid,
39-
size_t batch) {
40-
return cache.traverseAndPromoteItems(pid, cid, batch);
42+
static std::pair<size_t, double> getApproxUsage(C& cache,
43+
PoolId pid,
44+
ClassId cid) {
45+
const auto& pool = cache.getPool(pid);
46+
// we wait until all slabs are allocated before we start evicting
47+
if (!pool.allSlabsAllocated()) {
48+
return {0, 0.0};
49+
}
50+
return pool.getApproxUsage(cid);
4151
}
4252
};
4353

44-
enum class MoverDir { Evict = 0, Promote };
45-
4654
// Periodic worker that evicts items from tiers in batches
4755
// The primary aim is to reduce insertion times for new items in the
4856
// cache
4957
template <typename CacheT>
5058
class BackgroundMover : public PeriodicWorker {
5159
public:
60+
using ClassBgStatsType =
61+
std::map<MemoryDescriptorType, std::pair<size_t, size_t>>;
5262
using Cache = CacheT;
5363
// @param cache the cache interface
54-
// @param strategy the stragey class that defines how objects are
55-
// moved (promoted vs. evicted and how much)
64+
// @param evictionBatch number of items to evict in one go
65+
// @param promotionBatch number of items to promote in one go
66+
// @param targetFree target free percentage in the class
5667
BackgroundMover(Cache& cache,
57-
std::shared_ptr<BackgroundMoverStrategy> strategy,
58-
MoverDir direction_);
68+
size_t evictionBatch,
69+
size_t promotionBatch,
70+
double targetFree);
5971

6072
~BackgroundMover() override;
6173

6274
BackgroundMoverStats getStats() const noexcept;
63-
std::map<PoolId, std::map<ClassId, uint64_t>> getClassStats() const noexcept;
75+
ClassBgStatsType getPerClassStats() const noexcept { return movesPerClass_; }
6476

6577
void setAssignedMemory(std::vector<MemoryDescriptorType>&& assignedMemory);
6678

@@ -69,40 +81,75 @@ class BackgroundMover : public PeriodicWorker {
6981
static size_t workerId(PoolId pid, ClassId cid, size_t numWorkers);
7082

7183
private:
72-
std::map<PoolId, std::map<ClassId, uint64_t>> movesPerClass_;
84+
struct TraversalStats {
85+
// record a traversal over all assigned classes
86+
// and its time taken
87+
void recordTraversalTime(uint64_t nsTaken);
88+
89+
uint64_t getAvgTraversalTimeNs(uint64_t numTraversals) const;
90+
uint64_t getMinTraversalTimeNs() const { return minTraversalTimeNs_; }
91+
uint64_t getMaxTraversalTimeNs() const { return maxTraversalTimeNs_; }
92+
uint64_t getLastTraversalTimeNs() const { return lastTraversalTimeNs_; }
93+
94+
private:
95+
// time it took us the last time to traverse the cache.
96+
uint64_t lastTraversalTimeNs_{0};
97+
uint64_t minTraversalTimeNs_{std::numeric_limits<uint64_t>::max()};
98+
uint64_t maxTraversalTimeNs_{0};
99+
uint64_t totalTraversalTimeNs_{0};
100+
};
101+
102+
TraversalStats traversalStats_;
73103
// cache allocator's interface for evicting
74104
using Item = typename Cache::Item;
75105

76106
Cache& cache_;
77-
std::shared_ptr<BackgroundMoverStrategy> strategy_;
78-
MoverDir direction_;
79-
80-
std::function<size_t(Cache&, unsigned int, unsigned int, size_t)> moverFunc;
107+
uint8_t numTiers_{1}; // until we have multi-tier support
108+
size_t evictionBatch_{0};
109+
size_t promotionBatch_{0};
110+
double targetFree_{0.03};
81111

82112
// implements the actual logic of running the background evictor
83113
void work() override final;
84114
void checkAndRun();
85115

86-
AtomicCounter numMovedItems_{0};
87-
AtomicCounter numTraversals_{0};
88-
AtomicCounter totalBytesMoved_{0};
116+
// populates the toFree map for each class with the number of items to free
117+
std::map<MemoryDescriptorType, size_t> getNumItemsToFree(
118+
const std::vector<MemoryDescriptorType>& assignedMemory);
119+
120+
uint64_t numEvictedItems_{0};
121+
uint64_t numPromotedItems_{0};
122+
uint64_t numTraversals_{0};
123+
124+
ClassBgStatsType movesPerClass_;
89125

90126
std::vector<MemoryDescriptorType> assignedMemory_;
91127
folly::DistributedMutex mutex_;
92128
};
93129

94130
template <typename CacheT>
95-
BackgroundMover<CacheT>::BackgroundMover(
96-
Cache& cache,
97-
std::shared_ptr<BackgroundMoverStrategy> strategy,
98-
MoverDir direction)
99-
: cache_(cache), strategy_(strategy), direction_(direction) {
100-
if (direction_ == MoverDir::Evict) {
101-
moverFunc = BackgroundMoverAPIWrapper<CacheT>::traverseAndEvictItems;
102-
103-
} else if (direction_ == MoverDir::Promote) {
104-
moverFunc = BackgroundMoverAPIWrapper<CacheT>::traverseAndPromoteItems;
105-
}
131+
BackgroundMover<CacheT>::BackgroundMover(Cache& cache,
132+
size_t evictionBatch,
133+
size_t promotionBatch,
134+
double targetFree)
135+
: cache_(cache),
136+
evictionBatch_(evictionBatch),
137+
promotionBatch_(promotionBatch),
138+
targetFree_(targetFree) {}
139+
140+
template <typename CacheT>
141+
void BackgroundMover<CacheT>::TraversalStats::recordTraversalTime(
142+
uint64_t nsTaken) {
143+
lastTraversalTimeNs_ = nsTaken;
144+
minTraversalTimeNs_ = std::min(minTraversalTimeNs_, nsTaken);
145+
maxTraversalTimeNs_ = std::max(maxTraversalTimeNs_, nsTaken);
146+
totalTraversalTimeNs_ += nsTaken;
147+
}
148+
149+
template <typename CacheT>
150+
uint64_t BackgroundMover<CacheT>::TraversalStats::getAvgTraversalTimeNs(
151+
uint64_t numTraversals) const {
152+
return numTraversals ? totalTraversalTimeNs_ / numTraversals : 0;
106153
}
107154

108155
template <typename CacheT>
@@ -132,50 +179,89 @@ void BackgroundMover<CacheT>::setAssignedMemory(
132179
});
133180
}
134181

135-
// Look for classes that exceed the target memory capacity
136-
// and return those for eviction
182+
template <typename CacheT>
183+
std::map<MemoryDescriptorType, size_t>
184+
BackgroundMover<CacheT>::getNumItemsToFree(
185+
const std::vector<MemoryDescriptorType>& assignedMemory) {
186+
std::map<MemoryDescriptorType, size_t> toFree;
187+
for (const auto& md : assignedMemory) {
188+
const auto [pid, cid] = md;
189+
const auto& pool = cache_.getPool(pid);
190+
const auto [activeItems, usage] =
191+
BackgroundMoverAPIWrapper<CacheT>::getApproxUsage(cache_, pid, cid);
192+
if (usage < 1 - targetFree_) {
193+
toFree[md] = 0;
194+
} else {
195+
size_t maxItems = activeItems / usage;
196+
size_t targetItems = maxItems * (1 - targetFree_);
197+
size_t toFreeItems =
198+
activeItems > targetItems ? activeItems - targetItems : 0;
199+
toFree[md] = toFreeItems;
200+
}
201+
}
202+
return toFree;
203+
}
204+
137205
template <typename CacheT>
138206
void BackgroundMover<CacheT>::checkAndRun() {
139207
auto assignedMemory = mutex_.lock_combine([this] { return assignedMemory_; });
140-
141-
unsigned int moves = 0;
142-
auto batches = strategy_->calculateBatchSizes(cache_, assignedMemory);
143-
144-
for (size_t i = 0; i < batches.size(); i++) {
145-
const auto [pid, cid] = assignedMemory[i];
146-
const auto batch = batches[i];
147-
148-
if (batch == 0) {
149-
continue;
208+
auto toFree = getNumItemsToFree(assignedMemory); // calculate the number of
209+
// items to free
210+
while (true) {
211+
bool allDone = true;
212+
for (auto md : assignedMemory) {
213+
const auto [pid, cid] = md;
214+
size_t evictionBatch = evictionBatch_;
215+
size_t promotionBatch = 0; // will enable with multi-tier support
216+
if (toFree[md] == 0) {
217+
// no eviction work to be done since there is already at least
218+
// targetFree remaining in the class
219+
evictionBatch = 0;
220+
} else {
221+
allDone = false; // we still have some items to free
222+
}
223+
if (promotionBatch + evictionBatch > 0) {
224+
const auto begin = util::getCurrentTimeNs();
225+
// try moving BATCH items from the class in order to reach free target
226+
auto moved = BackgroundMoverAPIWrapper<CacheT>::traverseAndMoveItems(
227+
cache_, pid, cid, evictionBatch, promotionBatch);
228+
numEvictedItems_ += moved.first;
229+
toFree[md] > moved.first ? toFree[md] -= moved.first : toFree[md] = 0;
230+
numPromotedItems_ += moved.second;
231+
auto curr = movesPerClass_[md];
232+
curr.first += moved.first;
233+
curr.second += moved.second;
234+
movesPerClass_[md] = curr;
235+
numTraversals_++;
236+
auto end = util::getCurrentTimeNs();
237+
traversalStats_.recordTraversalTime(end > begin ? end - begin : 0);
238+
}
239+
}
240+
if (shouldStopWork() || allDone) {
241+
break;
150242
}
151-
152-
// try moving BATCH items from the class in order to reach free target
153-
auto moved = moverFunc(cache_, pid, cid, batch);
154-
moves += moved;
155-
movesPerClass_[pid][cid] += moved;
156-
totalBytesMoved_.add(moved * cache_.getPool(pid).getAllocSizes()[cid]);
157243
}
158-
159-
numTraversals_.inc();
160-
numMovedItems_.add(moves);
161244
}
162245

163246
template <typename CacheT>
164247
BackgroundMoverStats BackgroundMover<CacheT>::getStats() const noexcept {
165248
BackgroundMoverStats stats;
166-
stats.numMovedItems = numMovedItems_.get();
167-
stats.runCount = numTraversals_.get();
168-
stats.totalBytesMoved = totalBytesMoved_.get();
249+
stats.numEvictedItems = numEvictedItems_;
250+
stats.numPromotedItems = numPromotedItems_;
251+
stats.numTraversals = numTraversals_;
252+
stats.runCount = getRunCount();
253+
stats.avgItemsMoved =
254+
(double)(stats.numEvictedItems + stats.numPromotedItems) /
255+
(double)numTraversals_;
256+
stats.lastTraversalTimeNs = traversalStats_.getLastTraversalTimeNs();
257+
stats.avgTraversalTimeNs =
258+
traversalStats_.getAvgTraversalTimeNs(numTraversals_);
259+
stats.minTraversalTimeNs = traversalStats_.getMinTraversalTimeNs();
260+
stats.maxTraversalTimeNs = traversalStats_.getMaxTraversalTimeNs();
169261

170262
return stats;
171263
}
172264

173-
template <typename CacheT>
174-
std::map<PoolId, std::map<ClassId, uint64_t>>
175-
BackgroundMover<CacheT>::getClassStats() const noexcept {
176-
return movesPerClass_;
177-
}
178-
179265
template <typename CacheT>
180266
size_t BackgroundMover<CacheT>::workerId(PoolId pid,
181267
ClassId cid,
@@ -185,4 +271,4 @@ size_t BackgroundMover<CacheT>::workerId(PoolId pid,
185271
// TODO: came up with some better sharding (use hashing?)
186272
return (pid + cid) % numWorkers;
187273
}
188-
} // namespace facebook::cachelib
274+
}; // namespace facebook::cachelib

cachelib/allocator/BackgroundMoverStrategy.h

-48
This file was deleted.

cachelib/allocator/CMakeLists.txt

-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ add_library (cachelib_allocator
3535
CCacheManager.cpp
3636
ContainerTypes.cpp
3737
FreeMemStrategy.cpp
38-
FreeThresholdStrategy.cpp
3938
HitsPerSlabStrategy.cpp
4039
LruTailAgeStrategy.cpp
4140
MarginalHitsOptimizeStrategy.cpp

cachelib/allocator/Cache.h

+15
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,21 @@ enum class DestructorContext {
7373
kRemovedFromNVM
7474
};
7575

76+
// a tuple that describes the memory pool and allocation class
77+
struct MemoryDescriptorType {
78+
MemoryDescriptorType(PoolId pid, ClassId cid) : pid_(pid), cid_(cid) {}
79+
PoolId pid_;
80+
ClassId cid_;
81+
82+
bool operator<(const MemoryDescriptorType& rhs) const {
83+
return std::make_tuple(pid_, cid_) < std::make_tuple(rhs.pid_, rhs.cid_);
84+
}
85+
86+
bool operator==(const MemoryDescriptorType& rhs) const {
87+
return std::make_tuple(pid_, cid_) == std::make_tuple(rhs.pid_, rhs.cid_);
88+
}
89+
};
90+
7691
// A base class of cache exposing members and status agnostic of template type.
7792
class CacheBase {
7893
public:

0 commit comments

Comments
 (0)