mirror of
https://github.com/ClickHouse/ClickHouse.git
synced 2024-09-20 00:30:49 +00:00
Unify code in SLRU
This commit is contained in:
parent
272ae1d6ea
commit
b69e4541b9
@ -11,6 +11,10 @@ namespace ProfileEvents
|
||||
|
||||
namespace DB
|
||||
{
|
||||
namespace ErrorCodes
|
||||
{
|
||||
extern const int LOGICAL_ERROR;
|
||||
}
|
||||
|
||||
EvictionCandidates::~EvictionCandidates()
|
||||
{
|
||||
@ -88,8 +92,8 @@ void EvictionCandidates::evict()
|
||||
|
||||
void EvictionCandidates::finalize(FileCacheQueryLimit::QueryContext * query_context, const CachePriorityGuard::Lock & lock)
|
||||
{
|
||||
for (auto & holder : hold_space)
|
||||
holder->release();
|
||||
if (hold_space)
|
||||
hold_space->release();
|
||||
|
||||
chassert(lock.owns_lock());
|
||||
while (!queue_entries_to_invalidate.empty())
|
||||
@ -110,7 +114,10 @@ void EvictionCandidates::finalize(FileCacheQueryLimit::QueryContext * query_cont
|
||||
}
|
||||
|
||||
if (finalize_eviction_func)
|
||||
{
|
||||
finalize_eviction_func(lock);
|
||||
finalize_eviction_func = {};
|
||||
}
|
||||
}
|
||||
|
||||
void EvictionCandidates::setSpaceHolder(
|
||||
@ -119,8 +126,9 @@ void EvictionCandidates::setSpaceHolder(
|
||||
IFileCachePriority & priority,
|
||||
const CachePriorityGuard::Lock & lock)
|
||||
{
|
||||
auto holder = std::make_unique<IFileCachePriority::HoldSpace>(size, elements, priority, lock);
|
||||
hold_space.emplace_back(std::move(holder));
|
||||
if (hold_space)
|
||||
throw Exception(ErrorCodes::LOGICAL_ERROR, "Space hold is already set");
|
||||
hold_space = std::make_unique<IFileCachePriority::HoldSpace>(size, elements, priority, lock);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -41,7 +41,7 @@ private:
|
||||
size_t candidates_size = 0;
|
||||
FinalizeEvictionFunc finalize_eviction_func;
|
||||
std::vector<IFileCachePriority::IteratorPtr> queue_entries_to_invalidate;
|
||||
std::vector<IFileCachePriority::HoldSpacePtr> hold_space;
|
||||
IFileCachePriority::HoldSpacePtr hold_space;
|
||||
};
|
||||
|
||||
using EvictionCandidatesPtr = std::unique_ptr<EvictionCandidates>;
|
||||
|
@ -836,7 +836,7 @@ bool FileCache::tryReserve(
|
||||
if (query_priority)
|
||||
{
|
||||
if (!query_priority->collectCandidatesForEviction(
|
||||
size, reserve_stat, eviction_candidates, {}, user.user_id, cache_lock))
|
||||
size, 1, reserve_stat, eviction_candidates, {}, user.user_id, cache_lock))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@ -854,7 +854,7 @@ bool FileCache::tryReserve(
|
||||
chassert(!queue_iterator || file_segment.getReservedSize() > 0);
|
||||
|
||||
if (!main_priority->collectCandidatesForEviction(
|
||||
size, reserve_stat, eviction_candidates, queue_iterator, user.user_id, cache_lock))
|
||||
size, 1, reserve_stat, eviction_candidates, queue_iterator, user.user_id, cache_lock))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
@ -140,6 +140,7 @@ public:
|
||||
|
||||
virtual bool collectCandidatesForEviction(
|
||||
size_t size,
|
||||
size_t elements,
|
||||
FileCacheReserveStat & stat,
|
||||
EvictionCandidates & res,
|
||||
IteratorPtr reservee,
|
||||
|
@ -247,13 +247,14 @@ bool LRUFileCachePriority::canFit(
|
||||
|
||||
bool LRUFileCachePriority::collectCandidatesForEviction(
|
||||
size_t size,
|
||||
size_t elements,
|
||||
FileCacheReserveStat & stat,
|
||||
EvictionCandidates & res,
|
||||
IFileCachePriority::IteratorPtr /* reservee */,
|
||||
const UserID &,
|
||||
const CachePriorityGuard::Lock & lock)
|
||||
{
|
||||
if (canFit(size, 1, 0, 0, lock))
|
||||
if (canFit(size, elements, 0, 0, lock))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@ -281,7 +282,7 @@ bool LRUFileCachePriority::collectCandidatesForEviction(
|
||||
|
||||
auto can_fit = [&]
|
||||
{
|
||||
return canFit(size, 1, stat.total_stat.releasable_size, stat.total_stat.releasable_count, lock);
|
||||
return canFit(size, elements, stat.total_stat.releasable_size, stat.total_stat.releasable_count, lock);
|
||||
};
|
||||
|
||||
iterate([&](LockedKey & locked_key, const FileSegmentMetadataPtr & segment_metadata)
|
||||
@ -291,23 +292,17 @@ bool LRUFileCachePriority::collectCandidatesForEviction(
|
||||
|
||||
if (can_fit())
|
||||
{
|
||||
/// If we did not reach size limit (it means we reached only elements limit here)
|
||||
/// then we need to make sure that this fact that we fit in cache by size
|
||||
/// remains true after we release the lock and take it again.
|
||||
/// For this purpose we create a HoldSpace holder which makes sure that the space is hold in the meantime.
|
||||
/// We subtract reserve_stat.stat.releasable_size from the hold space,
|
||||
/// because it is the space that will be released, so we do not need to take it into account.
|
||||
const size_t hold_size = size > stat.total_stat.releasable_size
|
||||
? size - stat.total_stat.releasable_size
|
||||
: 0;
|
||||
|
||||
if (hold_size)
|
||||
{
|
||||
/// If we reached the elements limit - we will evict at least 1 element,
|
||||
/// then we do not need to hold anything, otherwise (if we reached limit only by size)
|
||||
/// we will also evict at least one element, so hold elements count is awlays zero here.
|
||||
res.setSpaceHolder(hold_size, 0, *this, lock);
|
||||
}
|
||||
const size_t hold_elements = elements > stat.total_stat.releasable_count
|
||||
? elements - stat.total_stat.releasable_count
|
||||
: 0;
|
||||
|
||||
if (hold_size || hold_elements)
|
||||
res.setSpaceHolder(hold_size, hold_elements, *this, lock);
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
|
@ -55,6 +55,7 @@ public:
|
||||
|
||||
bool collectCandidatesForEviction(
|
||||
size_t size,
|
||||
size_t elements,
|
||||
FileCacheReserveStat & stat,
|
||||
EvictionCandidates & res,
|
||||
IFileCachePriority::IteratorPtr reservee,
|
||||
|
@ -132,6 +132,7 @@ IFileCachePriority::IteratorPtr SLRUFileCachePriority::add( /// NOLINT
|
||||
|
||||
bool SLRUFileCachePriority::collectCandidatesForEviction(
|
||||
size_t size,
|
||||
size_t elements,
|
||||
FileCacheReserveStat & stat,
|
||||
EvictionCandidates & res,
|
||||
IFileCachePriority::IteratorPtr reservee,
|
||||
@ -142,8 +143,7 @@ bool SLRUFileCachePriority::collectCandidatesForEviction(
|
||||
/// for a corresponding file segment, so it will be directly put into probationary queue.
|
||||
if (!reservee)
|
||||
{
|
||||
return probationary_queue.collectCandidatesForEviction(
|
||||
size, stat, res, reservee, user_id, lock);
|
||||
return probationary_queue.collectCandidatesForEviction(size, elements, stat, res, reservee, user_id, lock);
|
||||
}
|
||||
|
||||
/// If `it` not nullptr (e.g. is already in some queue),
|
||||
@ -151,39 +151,45 @@ bool SLRUFileCachePriority::collectCandidatesForEviction(
|
||||
/// (in order to know where we need to free space).
|
||||
if (!assert_cast<SLRUIterator *>(reservee.get())->is_protected)
|
||||
{
|
||||
return probationary_queue.collectCandidatesForEviction(
|
||||
size, stat, res, reservee, user_id, lock);
|
||||
return probationary_queue.collectCandidatesForEviction(size, elements, stat, res, reservee, user_id, lock);
|
||||
}
|
||||
|
||||
/// Entry is in protected queue.
|
||||
/// Check if we have enough space in protected queue to fit a new size of entry.
|
||||
/// `size` is the increment to the current entry.size we want to increase.
|
||||
/// Here `elements` is 0, because entry is already in the protected queue.
|
||||
if (protected_queue.canFit(size, /* elements */0, lock))
|
||||
return collectCandidatesForEvictionInProtected(size, elements, stat, res, reservee, user_id, lock);
|
||||
}
|
||||
|
||||
bool SLRUFileCachePriority::collectCandidatesForEvictionInProtected(
|
||||
size_t size,
|
||||
size_t elements,
|
||||
FileCacheReserveStat & stat,
|
||||
EvictionCandidates & res,
|
||||
IFileCachePriority::IteratorPtr reservee,
|
||||
const UserID & user_id,
|
||||
const CachePriorityGuard::Lock & lock)
|
||||
{
|
||||
if (protected_queue.canFit(size, elements, lock))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/// If not enough space - we need to "downgrade" lowest priority entries from protected
|
||||
/// queue to probationary queue.
|
||||
/// The amount of such "downgraded" entries is equal to the amount
|
||||
/// required to make space for additionary `size` bytes for entry.
|
||||
auto downgrade_candidates = std::make_shared<EvictionCandidates>();
|
||||
/// If not enough space - we need to "downgrade" lowest priority entries
|
||||
/// from protected queue to probationary queue.
|
||||
|
||||
if (!protected_queue.collectCandidatesForEviction(
|
||||
size, stat, *downgrade_candidates, reservee, user_id, lock))
|
||||
auto downgrade_candidates = std::make_shared<EvictionCandidates>();
|
||||
FileCacheReserveStat downgrade_stat;
|
||||
if (!protected_queue.collectCandidatesForEviction(size, elements, downgrade_stat, *downgrade_candidates, reservee, user_id, lock))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
chassert(downgrade_candidates->size() > 0);
|
||||
|
||||
chassert(downgrade_candidates->size() > 0);
|
||||
|
||||
const size_t size_to_downgrade = stat.total_stat.releasable_size;
|
||||
chassert(size_to_downgrade);
|
||||
|
||||
FileCacheReserveStat probationary_stat;
|
||||
if (!probationary_queue.collectCandidatesForEviction(
|
||||
size_to_downgrade, probationary_stat, res, reservee, user_id, lock))
|
||||
downgrade_stat.total_stat.releasable_size, downgrade_stat.total_stat.releasable_count,
|
||||
stat, res, reservee, user_id, lock))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@ -192,7 +198,7 @@ bool SLRUFileCachePriority::collectCandidatesForEviction(
|
||||
{
|
||||
LOG_TEST(log, "Downgrading {} elements from protected to probationary. "
|
||||
"Total size: {}",
|
||||
downgrade_candidates->size(), stat.total_stat.releasable_size);
|
||||
downgrade_candidates->size(), downgrade_stat.total_stat.releasable_size);
|
||||
|
||||
for (const auto & [key, key_candidates] : *downgrade_candidates)
|
||||
{
|
||||
@ -261,88 +267,79 @@ void SLRUFileCachePriority::increasePriority(SLRUIterator & iterator, const Cach
|
||||
return;
|
||||
}
|
||||
|
||||
/// Check if there is enough space in protected queue to move entry there.
|
||||
/// If not - we need to "downgrade" lowest priority entries from protected
|
||||
/// queue to probationary queue.
|
||||
EvictionCandidates downgrade_candidates;
|
||||
FileCacheReserveStat downgrade_stat;
|
||||
|
||||
if (!protected_queue.collectCandidatesForEviction(
|
||||
entry_size, downgrade_stat, downgrade_candidates, {}, "", lock))
|
||||
{
|
||||
/// We cannot make space for entry to be moved to protected queue
|
||||
/// (not enough releasable file segments).
|
||||
/// Then just increase its priority within probationary queue.
|
||||
iterator.lru_iterator.increasePriority(lock);
|
||||
return;
|
||||
}
|
||||
|
||||
const size_t downgrade_size = downgrade_stat.total_stat.releasable_size;
|
||||
const size_t downgrade_count = downgrade_stat.total_stat.releasable_count;
|
||||
|
||||
/// Then we need to free up space in probationary for downgrade candidates,
|
||||
/// but we take into account that we'll remove reservee from probationary
|
||||
/// at the same time, so recalculate size which we need to free.
|
||||
size_t size_to_free = 0;
|
||||
if (downgrade_size && downgrade_size > entry_size)
|
||||
size_to_free = downgrade_size - entry_size;
|
||||
|
||||
LOG_TEST(log, "Will free up {} from probationary", size_to_free);
|
||||
|
||||
/// Now we need to check if those "downgrade" candidates can actually
|
||||
/// be moved to probationary queue.
|
||||
EvictionCandidates eviction_candidates;
|
||||
FileCacheReserveStat stat;
|
||||
|
||||
if (size_to_free)
|
||||
{
|
||||
if (!probationary_queue.collectCandidatesForEviction(
|
||||
size_to_free, stat, eviction_candidates, {}, {}, lock))
|
||||
{
|
||||
/// "downgrade" candidates cannot be moved to probationary queue,
|
||||
/// so entry cannot be moved to protected queue as well.
|
||||
/// Then just increase its priority within probationary queue.
|
||||
iterator.lru_iterator.increasePriority(lock);
|
||||
return;
|
||||
}
|
||||
/// Make space for "downgrade" candidates.
|
||||
eviction_candidates.evict();
|
||||
eviction_candidates.finalize(nullptr, lock);
|
||||
}
|
||||
|
||||
/// All checks passed, now we can move downgrade candidates to
|
||||
/// probationary queue and our entry to protected queue.
|
||||
|
||||
EntryPtr entry = iterator.getEntry();
|
||||
/// We need to remove the entry from probationary first
|
||||
/// in order to make space for downgrade from protected.
|
||||
iterator.lru_iterator.remove(lock);
|
||||
|
||||
if (!probationary_queue.canFit(downgrade_size, downgrade_count, lock))
|
||||
EvictionCandidates eviction_candidates;
|
||||
FileCacheReserveStat stat;
|
||||
try
|
||||
{
|
||||
throw Exception(ErrorCodes::LOGICAL_ERROR,
|
||||
"Cannot downgrade {} elements by total size of {}: not enough space: {}",
|
||||
downgrade_size, downgrade_count,
|
||||
probationary_queue.getStateInfoForLog(lock));
|
||||
/// Check if there is enough space in protected queue to move entry there.
|
||||
/// If not - we need to "downgrade" lowest priority entries from protected
|
||||
/// queue to probationary queue.
|
||||
///
|
||||
if (!collectCandidatesForEvictionInProtected(
|
||||
entry->size, 1, stat, eviction_candidates, nullptr, FileCache::getInternalUser().user_id, lock))
|
||||
{
|
||||
/// "downgrade" candidates cannot be moved to probationary queue,
|
||||
/// so entry cannot be moved to protected queue as well.
|
||||
/// Then just increase its priority within probationary queue.
|
||||
iterator.lru_iterator = addOrThrow(entry, probationary_queue, lock);
|
||||
return;
|
||||
}
|
||||
|
||||
eviction_candidates.evict();
|
||||
eviction_candidates.finalize(nullptr, lock);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
iterator.lru_iterator = addOrThrow(entry, probationary_queue, lock);
|
||||
throw;
|
||||
}
|
||||
|
||||
for (const auto & [key, key_candidates] : downgrade_candidates)
|
||||
{
|
||||
LOG_TEST(log, "Downgrading {} elements from protected to probationary. "
|
||||
"Total size: {}, current probationary state: {}",
|
||||
downgrade_candidates.size(), downgrade_size,
|
||||
probationary_queue.getStateInfoForLog(lock));
|
||||
|
||||
for (const auto & candidate : key_candidates.candidates)
|
||||
downgrade(candidate->getQueueIterator(), lock);
|
||||
}
|
||||
|
||||
downgrade_candidates.finalize(nullptr, lock);
|
||||
|
||||
iterator.lru_iterator = protected_queue.add(entry, lock);
|
||||
iterator.lru_iterator = addOrThrow(entry, protected_queue, lock);
|
||||
iterator.is_protected = true;
|
||||
}
|
||||
|
||||
LRUFileCachePriority::LRUIterator SLRUFileCachePriority::addOrThrow(
|
||||
EntryPtr entry, LRUFileCachePriority & queue, const CachePriorityGuard::Lock & lock)
|
||||
{
|
||||
try
|
||||
{
|
||||
return queue.add(entry, lock);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
const auto initial_exception = getCurrentExceptionMessage(true);
|
||||
try
|
||||
{
|
||||
/// We cannot allow a situation that a file exists on filesystem, but
|
||||
/// there is no corresponding entry in priority queue for it,
|
||||
/// because it will mean that cache became inconsistent.
|
||||
/// So let's try to fix the situation.
|
||||
auto metadata = entry->key_metadata->tryLock();
|
||||
chassert(metadata);
|
||||
if (metadata)
|
||||
{
|
||||
auto segment_metadata = metadata->tryGetByOffset(entry->offset);
|
||||
metadata->removeFileSegment(entry->offset, segment_metadata->file_segment->lock());
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
throw Exception(ErrorCodes::LOGICAL_ERROR,
|
||||
"Unexpected exception: {} (Initial exception: {}). Cache will become inconsistent",
|
||||
getCurrentExceptionMessage(true), initial_exception);
|
||||
}
|
||||
|
||||
/// Let's try to catch such cases in CI.
|
||||
chassert(false);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
IFileCachePriority::PriorityDumpPtr SLRUFileCachePriority::dump(const CachePriorityGuard::Lock & lock)
|
||||
{
|
||||
auto res = dynamic_pointer_cast<LRUFileCachePriority::LRUPriorityDump>(probationary_queue.dump(lock));
|
||||
|
@ -50,6 +50,7 @@ public:
|
||||
|
||||
bool collectCandidatesForEviction(
|
||||
size_t size,
|
||||
size_t elements,
|
||||
FileCacheReserveStat & stat,
|
||||
EvictionCandidates & res,
|
||||
IFileCachePriority::IteratorPtr reservee,
|
||||
@ -71,6 +72,20 @@ private:
|
||||
void increasePriority(SLRUIterator & iterator, const CachePriorityGuard::Lock & lock);
|
||||
|
||||
void downgrade(IteratorPtr iterator, const CachePriorityGuard::Lock &);
|
||||
|
||||
bool collectCandidatesForEvictionInProtected(
|
||||
size_t size,
|
||||
size_t elements,
|
||||
FileCacheReserveStat & stat,
|
||||
EvictionCandidates & res,
|
||||
IFileCachePriority::IteratorPtr reservee,
|
||||
const UserID & user_id,
|
||||
const CachePriorityGuard::Lock & lock);
|
||||
|
||||
LRUFileCachePriority::LRUIterator addOrThrow(
|
||||
EntryPtr entry,
|
||||
LRUFileCachePriority & queue,
|
||||
const CachePriorityGuard::Lock & lock);
|
||||
};
|
||||
|
||||
class SLRUFileCachePriority::SLRUIterator : public IFileCachePriority::Iterator
|
||||
|
Loading…
Reference in New Issue
Block a user