feat: add mergePool and score to eviction

This commit is contained in:
dualfroz committed 2026-09-02 15:33:24 +02:00
1 parent d3fe77badb
commit bcd5503f80
1 file changed
+102
+102
View File
@@ -155,3 +155,105 @@ defmodule Excache.Eviction do
{sampled, randState} = config.sampler.(remaining, config.sampleSize, config.randState)
config = %{config | randState: randState}
if random?(config.policy) do
{hd(sampled), pool, config}
else
metadata = config.metaFun.(Enum.map(sampled, & &1.key), config.now)
scored =
Enum.map(sampled, fn candidate ->
{score(config.policy, candidate, Map.get(metadata, candidate.key), config.now),
candidate}
end)
[{_score, victim} | rest] = mergePool(pool, scored, config.poolSize)
{victim, rest, config}
end
end
defp mergePool(pool, scored, poolSize) do
(scored ++ pool)
|> Enum.uniq_by(fn {_score, candidate} -> candidate.key end)
|> Enum.sort_by(fn {candidateScore, _candidate} -> candidateScore end, :desc)
|> Enum.take(poolSize)
end
defp score(policy, _candidate, meta, now) when policy in @lruPolicies, do: lruScore(meta, now)
defp score(policy, _candidate, meta, _now) when policy in @lfuPolicies,
do: @lfuMaxCounter - counterOf(meta)
defp score(:volatileTtl, candidate, _meta, _now), do: -candidate.expiresAt
defp lruScore(nil, _now), do: {1, 0}
defp lruScore(meta, now), do: {0, max(now - meta.lastAccess, 0)}
defp counterOf(nil), do: 0
defp counterOf(meta), do: meta.counter
defp eligible?(_candidate, :noeviction), do: false
defp eligible?(candidate, policy) do
if volatile?(policy), do: candidate.expiresAt != nil, else: true
end
defp emptyReason(policy) do
if volatile?(policy), do: :noVolatileKeys, else: :noCandidates
end
defp configure(opts) do
%{
policy: Keyword.fetch!(opts, :policy),
maxmemory: Keyword.get(opts, :maxmemory, 0),
now: Keyword.get_lazy(opts, :now, fn -> System.system_time(:millisecond) end),
sampleSize: Keyword.get(opts, :sampleSize, @defaultSampleSize),
poolSize: Keyword.get(opts, :poolSize, @defaultPoolSize),
sampler: Keyword.get(opts, :sampler, &sampleRandom/3),
randState: :rand.seed_s(:exsss, Keyword.get(opts, :seed, @defaultSeed)),
tracker: Keyword.get(opts, :tracker),
metaFun: metaFun(opts)
}
end
defp metaFun(opts) do
cond do
Keyword.has_key?(opts, :metadata) ->
metadata = Keyword.fetch!(opts, :metadata)
fn keys, _now -> Map.take(metadata, keys) end
Keyword.has_key?(opts, :tracker) ->
tracker = Keyword.fetch!(opts, :tracker)
fn keys, now -> Tracker.metadata(tracker, keys, now) end
true ->
fn _keys, _now -> %{} end
end
end
defp sampleRandom(pool, count, randState) when length(pool) <= count, do: {pool, randState}
defp sampleRandom(pool, count, randState) do
{taken, _rest, _size, nextState} =
Enum.reduce(1..count, {[], pool, length(pool), randState}, fn _index,
{acc, left, size, state} ->
{index, drawn} = :rand.uniform_s(size, state)
{picked, rest} = List.pop_at(left, index - 1)
{[picked | acc], rest, size - 1, drawn}
end)
{Enum.reverse(taken), nextState}
end
defp report(config, acc, reason) do
%{
reason: reason,
policy: config.policy,
maxmemory: config.maxmemory,
usedMemory: acc.used,
freedBytes: acc.freed,
evicted: Enum.reverse(acc.evicted),
evictedCount: length(acc.evicted),
passes: acc.passes
}
end
end