test: cover Excache.EvictionRuntimeTest and configure
This commit is contained in:
1 parent
94dccca6bc
commit
7d26219f61
1 file changed
+202
@@ -0,0 +1,202 @@
|
|||||||
|
defmodule Excache.EvictionRuntimeTest do
|
||||||
|
use Excache.Test.ServerCase, async: false
|
||||||
|
|
||||||
|
alias Excache.Aof
|
||||||
|
alias Excache.Config
|
||||||
|
alias Excache.Eviction.Runner
|
||||||
|
alias Excache.Eviction.Tracker
|
||||||
|
alias Excache.Store
|
||||||
|
|
||||||
|
@payload String.duplicate("x", 1000)
|
||||||
|
@oom {:error, "OOM command not allowed when used memory > 'maxmemory'"}
|
||||||
|
|
||||||
|
setup_all do
|
||||||
|
Application.put_env(:excache, :eviction_interval, 60_000)
|
||||||
|
Process.sleep(250)
|
||||||
|
|
||||||
|
on_exit(fn -> Application.delete_env(:excache, :eviction_interval) end)
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
|
||||||
|
setup do
|
||||||
|
Application.put_env(:excache, :eviction_recheck, 0)
|
||||||
|
Config.reset()
|
||||||
|
Tracker.clear(Tracker)
|
||||||
|
Runner.resetStats()
|
||||||
|
|
||||||
|
on_exit(fn ->
|
||||||
|
Config.reset()
|
||||||
|
Application.delete_env(:excache, :eviction_recheck)
|
||||||
|
end)
|
||||||
|
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
|
||||||
|
test "allkeys-lru drops the coldest keys when a write crosses maxmemory", %{conn: conn} do
|
||||||
|
conn = configure(conn, ["maxmemory-policy", "allkeys-lru", "maxmemory-samples", "64"])
|
||||||
|
conn = fill(conn, 1..40)
|
||||||
|
|
||||||
|
{before, conn} = stats(conn)
|
||||||
|
assert before["keys.count"] == 40
|
||||||
|
assert before["total.allocated"] > 40_000
|
||||||
|
|
||||||
|
Process.sleep(5)
|
||||||
|
conn = touch(conn, 1..10)
|
||||||
|
|
||||||
|
limit = div(before["total.allocated"], 2)
|
||||||
|
conn = configure(conn, ["maxmemory", Integer.to_string(limit)])
|
||||||
|
|
||||||
|
assert {{:simple, "OK"}, conn} = Client.command(conn, ["SET", "trigger", @payload])
|
||||||
|
|
||||||
|
{evicted, conn} = stats(conn)
|
||||||
|
freed = evicted["evicted.keys"]
|
||||||
|
|
||||||
|
assert freed >= 15 and freed <= 25
|
||||||
|
assert evicted["keys.count"] == 41 - freed
|
||||||
|
assert evicted["total.allocated"] < before["total.allocated"]
|
||||||
|
assert evicted["total.allocated"] <= limit + 2 * before["keys.bytes-per-key"]
|
||||||
|
assert evicted["oom.rejections"] == 0
|
||||||
|
assert Store.dbsize() == evicted["keys.count"]
|
||||||
|
assert Tracker.size(Tracker) == evicted["keys.count"]
|
||||||
|
|
||||||
|
assert {10, conn} = Client.command(conn, ["EXISTS" | keys(1..10)])
|
||||||
|
assert {cold, conn} = Client.command(conn, ["EXISTS" | keys(11..40)])
|
||||||
|
assert cold == 30 - freed
|
||||||
|
|
||||||
|
Aof.flush()
|
||||||
|
deletes = Aof.path() |> File.read!() |> String.split("$3\r\nDEL\r\n") |> length()
|
||||||
|
assert deletes - 1 == freed
|
||||||
|
|
||||||
|
{doctor, _conn} = Client.command(conn, ["MEMORY", "DOCTOR"])
|
||||||
|
assert doctor =~ "#{freed} keys have been evicted"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "noeviction refuses growing writes but keeps freeing ones", %{conn: conn} do
|
||||||
|
conn = fill(conn, 1..30)
|
||||||
|
{before, conn} = stats(conn)
|
||||||
|
limit = div(before["total.allocated"], 2)
|
||||||
|
conn = configure(conn, ["maxmemory", Integer.to_string(limit)])
|
||||||
|
|
||||||
|
assert {@oom, conn} = Client.command(conn, ["SET", "blocked", @payload])
|
||||||
|
assert {0, conn} = Client.command(conn, ["EXISTS", "blocked"])
|
||||||
|
assert {@oom, conn} = Client.command(conn, ["APPEND", "key:1", "more"])
|
||||||
|
assert {@oom, conn} = Client.command(conn, ["RPUSH", "fresh-list", "value"])
|
||||||
|
|
||||||
|
assert {@payload, conn} = Client.command(conn, ["GET", "key:1"])
|
||||||
|
assert {30, conn} = Client.command(conn, ["DBSIZE"])
|
||||||
|
|
||||||
|
{blocked, conn} = stats(conn)
|
||||||
|
assert blocked["evicted.keys"] == 0
|
||||||
|
assert blocked["oom.rejections"] >= 3
|
||||||
|
assert blocked["total.allocated"] == before["total.allocated"]
|
||||||
|
|
||||||
|
assert {20, conn} = Client.command(conn, ["DEL" | keys(1..20)])
|
||||||
|
assert {{:simple, "OK"}, conn} = Client.command(conn, ["SET", "allowed", @payload])
|
||||||
|
assert {11, conn} = Client.command(conn, ["DBSIZE"])
|
||||||
|
|
||||||
|
{recovered, _conn} = stats(conn)
|
||||||
|
assert recovered["total.allocated"] < limit
|
||||||
|
end
|
||||||
|
|
||||||
|
test "maxmemory 0 lifts the limit again", %{conn: conn} do
|
||||||
|
conn = fill(conn, 1..20)
|
||||||
|
{before, conn} = stats(conn)
|
||||||
|
conn = configure(conn, ["maxmemory", Integer.to_string(div(before["total.allocated"], 2))])
|
||||||
|
|
||||||
|
assert {@oom, conn} = Client.command(conn, ["SET", "blocked", @payload])
|
||||||
|
|
||||||
|
conn = configure(conn, ["maxmemory", "0"])
|
||||||
|
|
||||||
|
assert {{:simple, "OK"}, conn} = Client.command(conn, ["SET", "blocked", @payload])
|
||||||
|
assert {21, _conn} = Client.command(conn, ["DBSIZE"])
|
||||||
|
end
|
||||||
|
|
||||||
|
test "a volatile policy with no TTL anywhere cannot free a thing", %{conn: conn} do
|
||||||
|
conn = configure(conn, ["maxmemory-policy", "volatile-lru", "maxmemory-samples", "64"])
|
||||||
|
conn = fill(conn, 1..20)
|
||||||
|
|
||||||
|
{before, conn} = stats(conn)
|
||||||
|
limit = div(before["total.allocated"], 2)
|
||||||
|
conn = configure(conn, ["maxmemory", Integer.to_string(limit)])
|
||||||
|
|
||||||
|
assert {@oom, conn} = Client.command(conn, ["SET", "blocked", @payload])
|
||||||
|
assert {20, conn} = Client.command(conn, ["DBSIZE"])
|
||||||
|
|
||||||
|
conn =
|
||||||
|
Enum.reduce(1..15, conn, fn index, acc ->
|
||||||
|
{1, acc} = Client.command(acc, ["EXPIRE", "key:#{index}", "600"])
|
||||||
|
acc
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert {{:simple, "OK"}, conn} = Client.command(conn, ["SET", "allowed", @payload])
|
||||||
|
|
||||||
|
{after_, conn} = stats(conn)
|
||||||
|
assert after_["evicted.keys"] >= 10
|
||||||
|
assert after_["keys.count"] == 21 - after_["evicted.keys"]
|
||||||
|
assert {5, _conn} = Client.command(conn, ["EXISTS" | keys(16..20)])
|
||||||
|
end
|
||||||
|
|
||||||
|
test "a read refreshes access metadata only while a tracking policy is set", %{conn: conn} do
|
||||||
|
conn = fill(conn, 1..2)
|
||||||
|
assert Tracker.size(Tracker) == 0
|
||||||
|
|
||||||
|
conn = configure(conn, ["maxmemory-policy", "allkeys-lru"])
|
||||||
|
conn = fill(conn, 1..2)
|
||||||
|
assert Tracker.size(Tracker) == 2
|
||||||
|
|
||||||
|
Process.sleep(5)
|
||||||
|
conn = touch(conn, 1..1)
|
||||||
|
|
||||||
|
now = System.system_time(:millisecond)
|
||||||
|
assert Tracker.idleMs(Tracker, "key:1", now) < Tracker.idleMs(Tracker, "key:2", now)
|
||||||
|
|
||||||
|
conn = configure(conn, ["maxmemory-policy", "noeviction"])
|
||||||
|
Tracker.clear(Tracker)
|
||||||
|
_conn = touch(conn, 1..2)
|
||||||
|
assert Tracker.size(Tracker) == 0
|
||||||
|
end
|
||||||
|
|
||||||
|
test "a sweep drops metadata for keys that are gone", %{conn: conn} do
|
||||||
|
conn = configure(conn, ["maxmemory-policy", "allkeys-lru"])
|
||||||
|
conn = fill(conn, 1..10)
|
||||||
|
assert Tracker.size(Tracker) == 10
|
||||||
|
|
||||||
|
assert {5, conn} = Client.command(conn, ["DEL" | keys(1..5)])
|
||||||
|
assert Tracker.size(Tracker) == 10
|
||||||
|
|
||||||
|
conn = configure(conn, ["maxmemory", "1gb"])
|
||||||
|
assert {{:simple, "OK"}, conn} = Client.command(conn, ["SET", "fresh", "value"])
|
||||||
|
|
||||||
|
assert Store.dbsize() == 6
|
||||||
|
assert Tracker.size(Tracker) == 6
|
||||||
|
{reported, _conn} = stats(conn)
|
||||||
|
assert reported["tracker.keys"] == 6
|
||||||
|
assert reported["evicted.keys"] == 0
|
||||||
|
end
|
||||||
|
|
||||||
|
defp configure(conn, args) do
|
||||||
|
{{:simple, "OK"}, conn} = Client.command(conn, ["CONFIG", "SET" | args])
|
||||||
|
conn
|
||||||
|
end
|
||||||
|
|
||||||
|
defp fill(conn, range) do
|
||||||
|
Enum.reduce(range, conn, fn index, acc ->
|
||||||
|
{{:simple, "OK"}, acc} = Client.command(acc, ["SET", "key:#{index}", @payload])
|
||||||
|
acc
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp touch(conn, range) do
|
||||||
|
Enum.reduce(range, conn, fn index, acc ->
|
||||||
|
{@payload, acc} = Client.command(acc, ["GET", "key:#{index}"])
|
||||||
|
acc
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp keys(range), do: Enum.map(range, &"key:#{&1}")
|
||||||
|
|
||||||
|
defp stats(conn) do
|
||||||
|
{flat, conn} = Client.command(conn, ["MEMORY", "STATS"])
|
||||||
|
{flat |> Enum.chunk_every(2) |> Map.new(fn [name, value] -> {name, value} end), conn}
|
||||||
|
end
|
||||||
|
end
|
||||||
Reference in new issue
Block a user