Skip to content

Commit 2727c05

Browse files
byrnedjvinser52
authored andcommitted
CS Patch Part 2 for mulit-tier cachelib:
- transparent item movement - multi-tier combined locking with exclusive bit (pmem#38) with refactored incRef to support returning the result of markMoving (fail if already moving or exclusvie bit is set) option. - add tests (updated for numa bindings - post combined locking) for transparent item movement
1 parent 1ded485 commit 2727c05

File tree

7 files changed

+233
-25
lines changed

7 files changed

+233
-25
lines changed

cachelib/allocator/CacheAllocator-inl.h

Lines changed: 100 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ ShmSegmentOpts CacheAllocator<CacheTrait>::createShmCacheOpts(TierId tid) {
123123

124124
template <typename CacheTrait>
125125
size_t CacheAllocator<CacheTrait>::memoryTierSize(TierId tid) const {
126+
auto& memoryTierConfigs = config_.memoryTierConfigs;
126127
auto partitions = std::accumulate(memoryTierConfigs.begin(), memoryTierConfigs.end(), 0UL,
127128
[](const size_t i, const MemoryTierCacheConfig& config){
128129
return i + config.getRatio();
@@ -1231,7 +1232,7 @@ CacheAllocator<CacheTrait>::insertOrReplace(const WriteHandle& handle) {
12311232
* Concurrent threads which are getting handle to the same key:
12321233
* 1. When a handle is created it checks if the moving flag is set
12331234
* 2. If so, Handle implementation creates waitContext and adds it to the
1234-
* MoveCtx by calling handleWithWaitContextForMovingItem() method.
1235+
* MoveCtx by calling tryGetHandleWithWaitContextForMovingItem() method.
12351236
* 3. Wait until the moving thread will complete its job.
12361237
*/
12371238
template <typename CacheTrait>
@@ -1399,9 +1400,10 @@ CacheAllocator<CacheTrait>::getNextCandidate(TierId tid,
13991400
Item* toRecycle = nullptr;
14001401
Item* candidate = nullptr;
14011402
auto& mmContainer = getMMContainer(tid, pid, cid);
1403+
bool lastTier = tid+1 >= getNumTiers();
14021404

14031405
mmContainer.withEvictionIterator([this, pid, cid, &candidate, &toRecycle,
1404-
&searchTries, &mmContainer,
1406+
&searchTries, &mmContainer, &lastTier,
14051407
&token](auto&& itr) {
14061408
if (!itr) {
14071409
++searchTries;
@@ -1421,16 +1423,21 @@ CacheAllocator<CacheTrait>::getNextCandidate(TierId tid,
14211423
? &toRecycle_->asChainedItem().getParentItem(compressor_)
14221424
: toRecycle_;
14231425

1424-
auto putToken = createPutToken(*candidate_);
1426+
typename NvmCacheT::PutToken putToken;
1427+
if (lastTier) {
1428+
// if it's last tier, the item will be evicted
1429+
// need to create put token before marking it exclusive
1430+
putToken = createPutToken(*candidate_);
1431+
}
14251432

1426-
if (shouldWriteToNvmCache(*candidate_) && !putToken.isValid()) {
1433+
if (lastTier && shouldWriteToNvmCache(*candidate_) && !putToken.isValid()) {
14271434
stats_.evictFailConcurrentFill.inc();
14281435
++itr;
14291436
continue;
14301437
}
14311438

1432-
auto markedForEviction = candidate_->markForEviction();
1433-
if (!markedForEviction) {
1439+
auto marked = lastTier ? candidate_->markForEviction() : candidate_->markMoving();
1440+
if (!marked) {
14341441
if (candidate_->hasChainedItem()) {
14351442
stats_.evictFailParentAC.inc();
14361443
} else {
@@ -1440,8 +1447,10 @@ CacheAllocator<CacheTrait>::getNextCandidate(TierId tid,
14401447
continue;
14411448
}
14421449

1450+
XDCHECK(candidate_->isMoving() || candidate_->isMarkedForEviction());
14431451
// markForEviction to make sure no other thead is evicting the item
1444-
// nor holding a handle to that item
1452+
// nor holding a handle to that item if this is last tier
1453+
// since we won't be moving the item to the next tier
14451454
toRecycle = toRecycle_;
14461455
candidate = candidate_;
14471456
token = std::move(putToken);
@@ -1464,13 +1473,44 @@ CacheAllocator<CacheTrait>::getNextCandidate(TierId tid,
14641473

14651474
XDCHECK(toRecycle);
14661475
XDCHECK(candidate);
1467-
XDCHECK(candidate->isMarkedForEviction());
1476+
XDCHECK(candidate->isMoving() || candidate->isMarkedForEviction());
14681477

1469-
unlinkItemForEviction(*candidate);
1478+
auto evictedToNext = lastTier ? nullptr
1479+
: tryEvictToNextMemoryTier(*candidate, false);
1480+
if (!evictedToNext) {
1481+
if (!token.isValid()) {
1482+
token = createPutToken(*candidate);
1483+
}
1484+
// tryEvictToNextMemoryTier should only fail if allocation of the new item fails
1485+
// in that case, it should be still possible to mark item as exclusive.
1486+
//
1487+
// in case that we are on the last tier, we whould have already marked
1488+
// as exclusive since we will not be moving the item to the next tier
1489+
// but rather just evicting all together, no need to
1490+
// markExclusiveWhenMoving
1491+
auto ret = lastTier ? true : candidate->markForEvictionWhenMoving();
1492+
XDCHECK(ret);
1493+
1494+
unlinkItemForEviction(*candidate);
1495+
// wake up any readers that wait for the move to complete
1496+
// it's safe to do now, as we have the item marked exclusive and
1497+
// no other reader can be added to the waiters list
1498+
wakeUpWaiters(candidate->getKey(), {});
1499+
1500+
if (token.isValid() && shouldWriteToNvmCacheExclusive(*candidate)) {
1501+
nvmCache_->put(*candidate, std::move(token));
1502+
}
1503+
} else {
1504+
XDCHECK(!evictedToNext->isMarkedForEviction() && !evictedToNext->isMoving());
1505+
XDCHECK(!candidate->isMarkedForEviction() && !candidate->isMoving());
1506+
XDCHECK(!candidate->isAccessible());
1507+
XDCHECK(candidate->getKey() == evictedToNext->getKey());
14701508

1471-
if (token.isValid() && shouldWriteToNvmCacheExclusive(*candidate)) {
1472-
nvmCache_->put(*candidate, std::move(token));
1509+
wakeUpWaiters(candidate->getKey(), std::move(evictedToNext));
14731510
}
1511+
1512+
XDCHECK(!candidate->isMarkedForEviction() && !candidate->isMoving());
1513+
14741514
return {candidate, toRecycle};
14751515
}
14761516

@@ -1563,6 +1603,54 @@ bool CacheAllocator<CacheTrait>::shouldWriteToNvmCacheExclusive(
15631603
return true;
15641604
}
15651605

1606+
template <typename CacheTrait>
1607+
typename CacheAllocator<CacheTrait>::WriteHandle
1608+
CacheAllocator<CacheTrait>::tryEvictToNextMemoryTier(
1609+
TierId tid, PoolId pid, Item& item, bool fromBgThread) {
1610+
XDCHECK(item.isMoving());
1611+
XDCHECK(item.getRefCount() == 0);
1612+
if(item.hasChainedItem()) return WriteHandle{}; // TODO: We do not support ChainedItem yet
1613+
if(item.isExpired()) {
1614+
accessContainer_->remove(item);
1615+
item.unmarkMoving();
1616+
return acquire(&item);
1617+
}
1618+
1619+
TierId nextTier = tid; // TODO - calculate this based on some admission policy
1620+
while (++nextTier < getNumTiers()) { // try to evict down to the next memory tiers
1621+
// allocateInternal might trigger another eviction
1622+
auto newItemHdl = allocateInternalTier(nextTier, pid,
1623+
item.getKey(),
1624+
item.getSize(),
1625+
item.getCreationTime(),
1626+
item.getExpiryTime(),
1627+
fromBgThread);
1628+
1629+
if (newItemHdl) {
1630+
1631+
bool moveSuccess = moveRegularItem(item, newItemHdl);
1632+
if (!moveSuccess) {
1633+
return WriteHandle{};
1634+
}
1635+
XDCHECK_EQ(newItemHdl->getSize(), item.getSize());
1636+
item.unmarkMoving();
1637+
return newItemHdl;
1638+
} else {
1639+
return WriteHandle{};
1640+
}
1641+
}
1642+
1643+
return {};
1644+
}
1645+
1646+
template <typename CacheTrait>
1647+
typename CacheAllocator<CacheTrait>::WriteHandle
1648+
CacheAllocator<CacheTrait>::tryEvictToNextMemoryTier(Item& item, bool fromBgThread) {
1649+
auto tid = getTierId(item);
1650+
auto pid = allocator_[tid]->getAllocInfo(item.getMemory()).poolId;
1651+
return tryEvictToNextMemoryTier(tid, pid, item, fromBgThread);
1652+
}
1653+
15661654
template <typename CacheTrait>
15671655
typename CacheAllocator<CacheTrait>::RemoveRes
15681656
CacheAllocator<CacheTrait>::remove(typename Item::Key key) {
@@ -2088,8 +2176,7 @@ std::vector<std::string> CacheAllocator<CacheTrait>::dumpEvictionIterator(
20882176

20892177
std::vector<std::string> content;
20902178

2091-
size_t i = 0;
2092-
while (i < numItems && tid >= 0) {
2179+
while (tid >= 0) {
20932180
auto& mm = *mmContainers_[tid][pid][cid];
20942181
mm.withEvictionIterator([&content, numItems](auto&& itr) {
20952182
while (itr && content.size() < numItems) {
@@ -2099,7 +2186,6 @@ std::vector<std::string> CacheAllocator<CacheTrait>::dumpEvictionIterator(
20992186
});
21002187
--tid;
21012188
}
2102-
21032189
return content;
21042190
}
21052191

cachelib/allocator/CacheAllocator.h

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
#include <folly/fibers/TimedMutex.h>
2424
#include <folly/logging/xlog.h>
2525
#include <folly/synchronization/SanitizeThread.h>
26+
#include <folly/hash/Hash.h>
27+
#include <folly/container/F14Map.h>
2628
#include <gtest/gtest.h>
2729

2830
#include <chrono>
@@ -1572,15 +1574,6 @@ class CacheAllocator : public CacheBase {
15721574
// not exist.
15731575
FOLLY_ALWAYS_INLINE WriteHandle findFastImpl(Key key, AccessMode mode);
15741576

1575-
// Moves a regular item to a different memory tier.
1576-
//
1577-
// @param oldItem Reference to the item being moved
1578-
// @param newItemHdl Reference to the handle of the new item being moved into
1579-
//
1580-
// @return true If the move was completed, and the containers were updated
1581-
// successfully.
1582-
bool moveRegularItemOnEviction(Item& oldItem, WriteHandle& newItemHdl);
1583-
15841577
// Moves a regular item to a different slab. This should only be used during
15851578
// slab release after the item's exclusive bit has been set. The user supplied
15861579
// callback is responsible for copying the contents and fixing the semantics
@@ -1778,6 +1771,27 @@ class CacheAllocator : public CacheBase {
17781771

17791772
using EvictionIterator = typename MMContainer::LockedIterator;
17801773

1774+
// Try to move the item down to the next memory tier
1775+
//
1776+
// @param tid current tier ID of the item
1777+
// @param pid the pool ID the item belong to.
1778+
// @param item the item to evict
1779+
// @param fromBgThread whether this is called from BG thread
1780+
//
1781+
// @return valid handle to the item. This will be the last
1782+
// handle to the item. On failure an empty handle.
1783+
WriteHandle tryEvictToNextMemoryTier(TierId tid, PoolId pid, Item& item,
1784+
bool fromBgThread);
1785+
1786+
// Try to move the item down to the next memory tier
1787+
//
1788+
// @param item the item to evict
1789+
// @param fromBgThread whether this is called from BG thread
1790+
//
1791+
// @return valid handle to the item. This will be the last
1792+
// handle to the item. On failure an empty handle.
1793+
WriteHandle tryEvictToNextMemoryTier(Item& item, bool fromBgThread);
1794+
17811795
// Wakes up waiters if there are any
17821796
//
17831797
// @param item wakes waiters that are waiting on that item

cachelib/allocator/MMLru-inl.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,18 @@ void MMLru::Container<T, HookPtr>::withEvictionIterator(F&& fun) {
229229
}
230230
}
231231

232+
//template <typename T, MMLru::Hook<T> T::*HookPtr>
233+
//template <typename F>
234+
//void
235+
//MMLru::Container<T, HookPtr>::withPromotionIterator(F&& fun) {
236+
// if (config_.useCombinedLockForIterators) {
237+
// lruMutex_->lock_combine([this, &fun]() { fun(Iterator{lru_.begin()}); });
238+
// } else {
239+
// LockHolder lck{*lruMutex_};
240+
// fun(Iterator{lru_.begin()});
241+
// }
242+
//}
243+
232244
template <typename T, MMLru::Hook<T> T::*HookPtr>
233245
template <typename F>
234246
void MMLru::Container<T, HookPtr>::withContainerLock(F&& fun) {

cachelib/allocator/MMLru.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,7 @@ class MMLru {
235235
std::chrono::seconds mmReconfigureIntervalSecs{};
236236

237237
// Whether to use combined locking for withEvictionIterator.
238-
bool useCombinedLockForIterators{false};
238+
bool useCombinedLockForIterators{true};
239239
};
240240

241241
// The container object which can be used to keep track of objects of type

cachelib/allocator/tests/AllocatorMemoryTiersTest.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ using LruAllocatorMemoryTiersTest = AllocatorMemoryTiersTest<LruAllocator>;
2626
TEST_F(LruAllocatorMemoryTiersTest, MultiTiersInvalid) { this->testMultiTiersInvalid(); }
2727
TEST_F(LruAllocatorMemoryTiersTest, MultiTiersValid) { this->testMultiTiersValid(); }
2828
TEST_F(LruAllocatorMemoryTiersTest, MultiTiersValidMixed) { this->testMultiTiersValidMixed(); }
29+
TEST_F(LruAllocatorMemoryTiersTest, MultiTiersRemoveDuringEviction) { this->testMultiTiersRemoveDuringEviction(); }
30+
TEST_F(LruAllocatorMemoryTiersTest, MultiTiersReplaceDuringEviction) { this->testMultiTiersReplaceDuringEviction(); }
2931

3032
} // end of namespace tests
3133
} // end of namespace cachelib

cachelib/allocator/tests/AllocatorMemoryTiersTest.h

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,42 @@
2020
#include "cachelib/allocator/MemoryTierCacheConfig.h"
2121
#include "cachelib/allocator/tests/TestBase.h"
2222

23+
#include <folly/synchronization/Latch.h>
24+
2325
namespace facebook {
2426
namespace cachelib {
2527
namespace tests {
2628

2729
template <typename AllocatorT>
2830
class AllocatorMemoryTiersTest : public AllocatorTest<AllocatorT> {
31+
private:
32+
template<typename MvCallback>
33+
void testMultiTiersAsyncOpDuringMove(std::unique_ptr<AllocatorT>& alloc,
34+
PoolId& pool, bool& quit, MvCallback&& moveCb) {
35+
typename AllocatorT::Config config;
36+
config.setCacheSize(4 * Slab::kSize);
37+
config.enableCachePersistence("/tmp");
38+
config.configureMemoryTiers({
39+
MemoryTierCacheConfig::fromShm()
40+
.setRatio(1).setMemBind(std::string("0")),
41+
MemoryTierCacheConfig::fromShm()
42+
.setRatio(1).setMemBind(std::string("0"))
43+
});
44+
45+
config.enableMovingOnSlabRelease(moveCb, {} /* ChainedItemsMoveSync */,
46+
-1 /* movingAttemptsLimit */);
47+
48+
alloc = std::make_unique<AllocatorT>(AllocatorT::SharedMemNew, config);
49+
ASSERT(alloc != nullptr);
50+
pool = alloc->addPool("default", alloc->getCacheMemoryStats().ramCacheSize);
51+
52+
int i = 0;
53+
while(!quit) {
54+
auto handle = alloc->allocate(pool, std::to_string(++i), std::string("value").size());
55+
ASSERT(handle != nullptr);
56+
ASSERT_NO_THROW(alloc->insertOrReplace(handle));
57+
}
58+
}
2959
public:
3060
void testMultiTiersInvalid() {
3161
typename AllocatorT::Config config;
@@ -74,6 +104,70 @@ class AllocatorMemoryTiersTest : public AllocatorTest<AllocatorT> {
74104
ASSERT(handle != nullptr);
75105
ASSERT_NO_THROW(alloc->insertOrReplace(handle));
76106
}
107+
108+
void testMultiTiersRemoveDuringEviction() {
109+
std::unique_ptr<AllocatorT> alloc;
110+
PoolId pool;
111+
std::unique_ptr<std::thread> t;
112+
folly::Latch latch(1);
113+
bool quit = false;
114+
115+
auto moveCb = [&] (typename AllocatorT::Item& oldItem,
116+
typename AllocatorT::Item& newItem,
117+
typename AllocatorT::Item* /* parentPtr */) {
118+
119+
auto key = oldItem.getKey();
120+
t = std::make_unique<std::thread>([&](){
121+
// remove() function is blocked by wait context
122+
// till item is moved to next tier. So that, we should
123+
// notify latch before calling remove()
124+
latch.count_down();
125+
alloc->remove(key);
126+
});
127+
// wait till async thread is running
128+
latch.wait();
129+
memcpy(newItem.getMemory(), oldItem.getMemory(), oldItem.getSize());
130+
quit = true;
131+
};
132+
133+
testMultiTiersAsyncOpDuringMove(alloc, pool, quit, moveCb);
134+
135+
t->join();
136+
}
137+
138+
void testMultiTiersReplaceDuringEviction() {
139+
std::unique_ptr<AllocatorT> alloc;
140+
PoolId pool;
141+
std::unique_ptr<std::thread> t;
142+
folly::Latch latch(1);
143+
bool quit = false;
144+
145+
auto moveCb = [&] (typename AllocatorT::Item& oldItem,
146+
typename AllocatorT::Item& newItem,
147+
typename AllocatorT::Item* /* parentPtr */) {
148+
auto key = oldItem.getKey();
149+
if(!quit) {
150+
// we need to replace only once because subsequent allocate calls
151+
// will cause evictions recursevly
152+
quit = true;
153+
t = std::make_unique<std::thread>([&](){
154+
auto handle = alloc->allocate(pool, key, std::string("new value").size());
155+
// insertOrReplace() function is blocked by wait context
156+
// till item is moved to next tier. So that, we should
157+
// notify latch before calling insertOrReplace()
158+
latch.count_down();
159+
ASSERT_NO_THROW(alloc->insertOrReplace(handle));
160+
});
161+
// wait till async thread is running
162+
latch.wait();
163+
}
164+
memcpy(newItem.getMemory(), oldItem.getMemory(), oldItem.getSize());
165+
};
166+
167+
testMultiTiersAsyncOpDuringMove(alloc, pool, quit, moveCb);
168+
169+
t->join();
170+
}
77171
};
78172
} // namespace tests
79173
} // namespace cachelib

0 commit comments

Comments
 (0)