Skip to content

Commit 5977cb9

Browse files
vinser52igchor
authored andcommitted
Implemented async Item movement between tiers
1 parent c10403a commit 5977cb9

File tree

7 files changed

+386
-6
lines changed

7 files changed

+386
-6
lines changed

cachelib/allocator/CacheAllocator-inl.h

Lines changed: 217 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ CacheAllocator<CacheTrait>::CacheAllocator(Config config)
4747
[this](Item* it) -> ItemHandle { return acquire(it); })),
4848
chainedItemLocks_(config_.chainedItemsLockPower,
4949
std::make_shared<MurmurHash2>()),
50+
movesMap_(kShards),
51+
moveLock_(kShards),
5052
cacheCreationTime_{util::getCurrentTimeSec()} {
5153

5254
if (numTiers_ > 1 || std::holds_alternative<FileShmSegmentOpts>(
@@ -133,6 +135,8 @@ CacheAllocator<CacheTrait>::CacheAllocator(SharedMemNewT, Config config)
133135
[this](Item* it) -> ItemHandle { return acquire(it); })),
134136
chainedItemLocks_(config_.chainedItemsLockPower,
135137
std::make_shared<MurmurHash2>()),
138+
movesMap_(kShards),
139+
moveLock_(kShards),
136140
cacheCreationTime_{util::getCurrentTimeSec()} {
137141
initCommon(false);
138142
shmManager_->removeShm(detail::kShmInfoName,
@@ -169,6 +173,8 @@ CacheAllocator<CacheTrait>::CacheAllocator(SharedMemAttachT, Config config)
169173
[this](Item* it) -> ItemHandle { return acquire(it); })),
170174
chainedItemLocks_(config_.chainedItemsLockPower,
171175
std::make_shared<MurmurHash2>()),
176+
movesMap_(kShards),
177+
moveLock_(kShards),
172178
cacheCreationTime_{*metadata_.cacheCreationTime_ref()} {
173179
/* TODO - per tier? */
174180
for (auto pid : *metadata_.compactCachePools_ref()) {
@@ -970,6 +976,25 @@ bool CacheAllocator<CacheTrait>::replaceInMMContainer(Item& oldItem,
970976
}
971977
}
972978

979+
template <typename CacheTrait>
980+
bool CacheAllocator<CacheTrait>::replaceInMMContainer(Item* oldItem,
981+
Item& newItem) {
982+
return replaceInMMContainer(*oldItem, newItem);
983+
}
984+
985+
template <typename CacheTrait>
986+
bool CacheAllocator<CacheTrait>::replaceInMMContainer(EvictionIterator& oldItemIt,
987+
Item& newItem) {
988+
auto& oldContainer = getMMContainer(*oldItemIt);
989+
auto& newContainer = getMMContainer(newItem);
990+
991+
// This function is used for eviction across tiers
992+
XDCHECK(&oldContainer != &newContainer);
993+
oldContainer.remove(oldItemIt);
994+
995+
return newContainer.add(newItem);
996+
}
997+
973998
template <typename CacheTrait>
974999
bool CacheAllocator<CacheTrait>::replaceChainedItemInMMContainer(
9751000
Item& oldItem, Item& newItem) {
@@ -1104,6 +1129,157 @@ CacheAllocator<CacheTrait>::insertOrReplace(const ItemHandle& handle) {
11041129
return replaced;
11051130
}
11061131

1132+
/* Next two methods are used to asynchronously move Item between memory tiers.
1133+
*
1134+
* The thread, which moves Item, allocates new Item in the tier we are moving to
1135+
* and calls moveRegularItemOnEviction() method. This method does the following:
1136+
* 1. Create MoveCtx and put it to the movesMap.
1137+
* 2. Update the access container with the new item from the tier we are
1138+
* moving to. This Item has kIncomplete flag set.
1139+
* 3. Copy data from the old Item to the new one.
1140+
* 4. Unset the kIncomplete flag and Notify MoveCtx
1141+
*
1142+
* Concurrent threads which are getting handle to the same key:
1143+
* 1. When a handle is created it checks if the kIncomplete flag is set
1144+
* 2. If so, Handle implementation creates waitContext and adds it to the
1145+
* MoveCtx by calling addWaitContextForMovingItem() method.
1146+
* 3. Wait until the moving thread will complete its job.
1147+
*/
1148+
template <typename CacheTrait>
1149+
bool CacheAllocator<CacheTrait>::addWaitContextForMovingItem(
1150+
folly::StringPiece key, std::shared_ptr<WaitContext<ItemHandle>> waiter) {
1151+
auto shard = getShardForKey(key);
1152+
auto& movesMap = getMoveMapForShard(shard);
1153+
auto lock = getMoveLockForShard(shard);
1154+
auto it = movesMap.find(key);
1155+
if (it == movesMap.end()) {
1156+
return false;
1157+
}
1158+
auto ctx = it->second.get();
1159+
ctx->addWaiter(std::move(waiter));
1160+
return true;
1161+
}
1162+
1163+
template <typename CacheTrait>
1164+
template <typename ItemPtr>
1165+
typename CacheAllocator<CacheTrait>::ItemHandle
1166+
CacheAllocator<CacheTrait>::moveRegularItemOnEviction(
1167+
ItemPtr& oldItemPtr, ItemHandle& newItemHdl) {
1168+
// TODO: should we introduce new latency tracker. E.g. evictRegularLatency_
1169+
// ??? util::LatencyTracker tracker{stats_.evictRegularLatency_};
1170+
1171+
Item& oldItem = *oldItemPtr;
1172+
if (!oldItem.isAccessible() || oldItem.isExpired()) {
1173+
return {};
1174+
}
1175+
1176+
XDCHECK_EQ(newItemHdl->getSize(), oldItem.getSize());
1177+
XDCHECK_NE(getTierId(oldItem), getTierId(*newItemHdl));
1178+
1179+
// take care of the flags before we expose the item to be accessed. this
1180+
// will ensure that when another thread removes the item from RAM, we issue
1181+
// a delete accordingly. See D7859775 for an example
1182+
if (oldItem.isNvmClean()) {
1183+
newItemHdl->markNvmClean();
1184+
}
1185+
1186+
folly::StringPiece key(oldItem.getKey());
1187+
auto shard = getShardForKey(key);
1188+
auto& movesMap = getMoveMapForShard(shard);
1189+
MoveCtx* ctx(nullptr);
1190+
{
1191+
auto lock = getMoveLockForShard(shard);
1192+
auto res = movesMap.try_emplace(key, std::make_unique<MoveCtx>());
1193+
if (!res.second) {
1194+
return {};
1195+
}
1196+
ctx = res.first->second.get();
1197+
}
1198+
1199+
auto resHdl = ItemHandle{};
1200+
auto guard = folly::makeGuard([key, this, ctx, shard, &resHdl]() {
1201+
auto& movesMap = getMoveMapForShard(shard);
1202+
if (resHdl)
1203+
resHdl->unmarkIncomplete();
1204+
auto lock = getMoveLockForShard(shard);
1205+
ctx->setItemHandle(std::move(resHdl));
1206+
movesMap.erase(key);
1207+
});
1208+
1209+
// TODO: Possibly we can use markMoving() instead. But today
1210+
// moveOnSlabRelease logic assume that we mark as moving old Item
1211+
// and than do copy and replace old Item with the new one in access
1212+
// container. Furthermore, Item can be marked as Moving only
1213+
// if it is linked to MM container. In our case we mark the new Item
1214+
// and update access container before the new Item is ready (content is
1215+
// copied).
1216+
newItemHdl->markIncomplete();
1217+
1218+
// Inside the access container's lock, this checks if the old item is
1219+
// accessible and its refcount is zero. If the item is not accessible,
1220+
// there is no point to replace it since it had already been removed
1221+
// or in the process of being removed. If the item is in cache but the
1222+
// refcount is non-zero, it means user could be attempting to remove
1223+
// this item through an API such as remove(ItemHandle). In this case,
1224+
// it is unsafe to replace the old item with a new one, so we should
1225+
// also abort.
1226+
if (!accessContainer_->replaceIf(oldItem, *newItemHdl,
1227+
itemEvictionPredicate)) {
1228+
return {};
1229+
}
1230+
1231+
if (config_.moveCb) {
1232+
// Execute the move callback. We cannot make any guarantees about the
1233+
// consistency of the old item beyond this point, because the callback can
1234+
// do more than a simple memcpy() e.g. update external references. If there
1235+
// are any remaining handles to the old item, it is the caller's
1236+
// responsibility to invalidate them. The move can only fail after this
1237+
// statement if the old item has been removed or replaced, in which case it
1238+
// should be fine for it to be left in an inconsistent state.
1239+
config_.moveCb(oldItem, *newItemHdl, nullptr);
1240+
} else {
1241+
std::memcpy(newItemHdl->getWritableMemory(), oldItem.getMemory(),
1242+
oldItem.getSize());
1243+
}
1244+
1245+
// Inside the MM container's lock, this checks if the old item exists to
1246+
// make sure that no other thread removed it, and only then replaces it.
1247+
if (!replaceInMMContainer(oldItemPtr, *newItemHdl)) {
1248+
accessContainer_->remove(*newItemHdl);
1249+
return {};
1250+
}
1251+
1252+
// Replacing into the MM container was successful, but someone could have
1253+
// called insertOrReplace() or remove() before or after the
1254+
// replaceInMMContainer() operation, which would invalidate newItemHdl.
1255+
if (!newItemHdl->isAccessible()) {
1256+
removeFromMMContainer(*newItemHdl);
1257+
return {};
1258+
}
1259+
1260+
// no one can add or remove chained items at this point
1261+
if (oldItem.hasChainedItem()) {
1262+
// safe to acquire handle for a moving Item
1263+
auto oldHandle = acquire(&oldItem);
1264+
XDCHECK_EQ(1u, oldHandle->getRefCount()) << oldHandle->toString();
1265+
XDCHECK(!newItemHdl->hasChainedItem()) << newItemHdl->toString();
1266+
try {
1267+
auto l = chainedItemLocks_.lockExclusive(oldItem.getKey());
1268+
transferChainLocked(oldHandle, newItemHdl);
1269+
} catch (const std::exception& e) {
1270+
// this should never happen because we drained all the handles.
1271+
XLOGF(DFATAL, "{}", e.what());
1272+
throw;
1273+
}
1274+
1275+
XDCHECK(!oldItem.hasChainedItem());
1276+
XDCHECK(newItemHdl->hasChainedItem());
1277+
}
1278+
newItemHdl.unmarkNascent();
1279+
resHdl = std::move(newItemHdl); // guard will assign it to ctx under lock
1280+
return acquire(&oldItem);
1281+
}
1282+
11071283
template <typename CacheTrait>
11081284
bool CacheAllocator<CacheTrait>::moveRegularItem(Item& oldItem,
11091285
ItemHandle& newItemHdl) {
@@ -1358,10 +1534,47 @@ bool CacheAllocator<CacheTrait>::shouldWriteToNvmCacheExclusive(
13581534
return true;
13591535
}
13601536

1537+
template <typename CacheTrait>
1538+
template <typename ItemPtr>
1539+
typename CacheAllocator<CacheTrait>::ItemHandle
1540+
CacheAllocator<CacheTrait>::tryEvictToNextMemoryTier(
1541+
TierId tid, PoolId pid, ItemPtr& item) {
1542+
if(item->isExpired()) return acquire(item);
1543+
1544+
TierId nextTier = tid; // TODO - calculate this based on some admission policy
1545+
while (++nextTier < numTiers_) { // try to evict down to the next memory tiers
1546+
// allocateInternal might trigger another eviction
1547+
auto newItemHdl = allocateInternalTier(nextTier, pid,
1548+
item->getKey(),
1549+
item->getSize(),
1550+
item->getCreationTime(),
1551+
item->getExpiryTime());
1552+
1553+
if (newItemHdl) {
1554+
XDCHECK_EQ(newItemHdl->getSize(), item->getSize());
1555+
1556+
return moveRegularItemOnEviction(item, newItemHdl);
1557+
}
1558+
}
1559+
1560+
return {};
1561+
}
1562+
1563+
template <typename CacheTrait>
1564+
typename CacheAllocator<CacheTrait>::ItemHandle
1565+
CacheAllocator<CacheTrait>::tryEvictToNextMemoryTier(Item* item) {
1566+
auto tid = getTierId(*item);
1567+
auto pid = allocator_[tid]->getAllocInfo(item->getMemory()).poolId;
1568+
return tryEvictToNextMemoryTier(tid, pid, item);
1569+
}
1570+
13611571
template <typename CacheTrait>
13621572
typename CacheAllocator<CacheTrait>::ItemHandle
13631573
CacheAllocator<CacheTrait>::advanceIteratorAndTryEvictRegularItem(
13641574
TierId tid, PoolId pid, MMContainer& mmContainer, EvictionIterator& itr) {
1575+
auto evictHandle = tryEvictToNextMemoryTier(tid, pid, itr);
1576+
if(evictHandle) return evictHandle;
1577+
13651578
Item& item = *itr;
13661579

13671580
const bool evictToNvmCache = shouldWriteToNvmCache(item);
@@ -1380,7 +1593,7 @@ CacheAllocator<CacheTrait>::advanceIteratorAndTryEvictRegularItem(
13801593
// if we remove the item from both access containers and mm containers
13811594
// below, we will need a handle to ensure proper cleanup in case we end up
13821595
// not evicting this item
1383-
auto evictHandle = accessContainer_->removeIf(item, &itemEvictionPredicate);
1596+
evictHandle = accessContainer_->removeIf(item, &itemEvictionPredicate);
13841597

13851598
if (!evictHandle) {
13861599
++itr;
@@ -2717,6 +2930,9 @@ CacheAllocator<CacheTrait>::evictNormalItemForSlabRelease(Item& item) {
27172930
return ItemHandle{};
27182931
}
27192932

2933+
auto evictHandle = tryEvictToNextMemoryTier(&item);
2934+
if(evictHandle) return evictHandle;
2935+
27202936
auto predicate = [](const Item& it) { return it.getRefCount() == 0; };
27212937

27222938
const bool evictToNvmCache = shouldWriteToNvmCache(item);

0 commit comments

Comments
 (0)