cachelib/allocator/LruTailAgeStrategy.cpp (106 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/allocator/LruTailAgeStrategy.h"
#include <folly/logging/xlog.h>
#include <algorithm>
#include <functional>
#include "cachelib/allocator/Util.h"
namespace facebook {
namespace cachelib {
LruTailAgeStrategy::LruTailAgeStrategy(Config config)
: RebalanceStrategy(LruTailAge), config_(std::move(config)) {}
// The list of allocation classes to be rebalanced is determined by:
//
// 0. Filter out classes that have below minSlabThreshold_
//
// 1. Filter out classes that have just gained a slab recently
//
// 2. Compute weighted tail age from all the remaining ACs
//
// 3. Pick an AC with the oldest tail age higher than the weighted average
ClassId LruTailAgeStrategy::pickVictim(
const Config& config,
PoolId pid,
const PoolStats& poolStats,
const PoolEvictionAgeStats& poolEvictionAgeStats) {
auto victims = poolStats.getClassIds();
// ignore allocation classes that have fewer than the threshold of slabs.
victims =
filterByNumEvictableSlabs(poolStats, std::move(victims), config.minSlabs);
// ignore allocation classes that recently gained a slab. These will be
// growing in their eviction age and we want to let the evicitons stabilize
// before we consider them again.
victims = filterVictimsByHoldOff(pid, poolStats, std::move(victims));
if (victims.empty()) {
XLOG(DBG, "Rebalancing: No victims available");
return Slab::kInvalidClassId;
}
auto victimClassId = pickVictimByFreeMem(
victims, poolStats, config.getFreeMemThreshold(), getPoolState(pid));
if (victimClassId != Slab::kInvalidClassId) {
return victimClassId;
}
// the oldest projected age among the victims
return *std::max_element(
victims.begin(), victims.end(), [&](ClassId a, ClassId b) {
return (
poolEvictionAgeStats.getProjectedAge(a) *
(config.getWeight ? config.getWeight(pid, a, poolStats) : 1.0) <
poolEvictionAgeStats.getProjectedAge(b) *
(config.getWeight ? config.getWeight(pid, b, poolStats) : 1.0));
});
}
ClassId LruTailAgeStrategy::pickReceiver(
const Config& config,
PoolId pid,
const PoolStats& stats,
ClassId victim,
const PoolEvictionAgeStats& poolEvictionAgeStats) const {
auto receivers = stats.getClassIds();
receivers.erase(victim);
receivers = filterByNoEvictions(stats, receivers, getPoolState(pid));
if (receivers.empty()) {
return Slab::kInvalidClassId;
}
// the youngest age among the potenital receivers
return *std::min_element(
receivers.begin(), receivers.end(), [&](ClassId a, ClassId b) {
return (poolEvictionAgeStats.getOldestElementAge(a) *
(config.getWeight ? config.getWeight(pid, a, stats) : 1.0) <
poolEvictionAgeStats.getOldestElementAge(b) *
(config.getWeight ? config.getWeight(pid, b, stats) : 1.0));
});
}
RebalanceContext LruTailAgeStrategy::pickVictimAndReceiverImpl(
const CacheBase& cache, PoolId pid) {
if (!cache.getPool(pid).allSlabsAllocated()) {
XLOGF(DBG,
"Pool Id: {}"
" does not have all its slabs allocated"
" and does not need rebalancing.",
static_cast<int>(pid));
return kNoOpContext;
}
const auto config = getConfigCopy();
const auto poolStats = cache.getPoolStats(pid);
const auto poolEvictionAgeStats =
cache.getPoolEvictionAgeStats(pid, config.slabProjectionLength);
RebalanceContext ctx;
ctx.victimClassId = pickVictim(config, pid, poolStats, poolEvictionAgeStats);
ctx.receiverClassId = pickReceiver(config, pid, poolStats, ctx.victimClassId,
poolEvictionAgeStats);
if (ctx.victimClassId == ctx.receiverClassId ||
ctx.victimClassId == Slab::kInvalidClassId ||
ctx.receiverClassId == Slab::kInvalidClassId) {
return kNoOpContext;
}
if (!config.getWeight) {
const auto victimProjectedTailAge =
poolEvictionAgeStats.getProjectedAge(ctx.victimClassId);
const auto receiverTailAge =
poolEvictionAgeStats.getOldestElementAge(ctx.receiverClassId);
XLOGF(DBG, "Rebalancing: receiver = {}, receiverTailAge = {}, victim = {}",
static_cast<int>(ctx.receiverClassId), receiverTailAge,
static_cast<int>(ctx.victimClassId));
const auto improvement = victimProjectedTailAge - receiverTailAge;
if (victimProjectedTailAge < receiverTailAge ||
improvement < config.minTailAgeDifference ||
improvement < config.tailAgeDifferenceRatio *
static_cast<long double>(victimProjectedTailAge)) {
return kNoOpContext;
}
}
// start a hold off so that the receiver does not become a victim soon
// enough.
getPoolState(pid).at(ctx.receiverClassId).startHoldOff();
return ctx;
}
ClassId LruTailAgeStrategy::pickVictimImpl(const CacheBase& cache, PoolId pid) {
const auto config = getConfigCopy();
const auto poolEvictionAgeStats =
cache.getPoolEvictionAgeStats(pid, config.slabProjectionLength);
return pickVictim(config, pid, cache.getPoolStats(pid), poolEvictionAgeStats);
}
} // namespace cachelib
} // namespace facebook