new cache design (based on multi-page idea)

This commit is contained in:
Dmitry Kolesnikov 2013-04-16 12:15:14 +03:00
parent 20544f1bd1
commit d324600f4b
12 changed files with 632 additions and 361 deletions

1
.gitignore vendored
View File

@ -8,5 +8,6 @@
ebin/
deps/
.eunit/
tests/
rebar
*.sublime-*

7
Emakefile Normal file
View File

@ -0,0 +1,7 @@
{"src/*", [
report,
verbose,
{i, "include"},
{outdir, "ebin"},
debug_info
]}.

View File

@ -1,5 +1,7 @@
.PHONY: deps test
BB=../basho_bench
all: rebar deps compile
compile:
@ -31,3 +33,9 @@ rebar:
curl -O http://cloud.github.com/downloads/basho/rebar/rebar
chmod ugo+x rebar
benchmark:
$(BB)/basho_bench priv/cache.benchmark
$(BB)/priv/summary.r -i tests/current
open tests/current/summary.png

26
priv/cache.benchmark Normal file
View File

@ -0,0 +1,26 @@
{code_paths, ["./ebin"]}.
{log_level, info}.
{report_interval, 1}.
{driver, cache_benchmark}.
%%
%% workload
{mode, max}.
{duration, 1}.
{concurrent, 10}.
{key_generator, {uniform_int, 1000000}}.
{value_generator, {fixed_bin, 1000}}.
{operations, [
{put, 5}
,{get, 5}
%,{remove, 1}
]}.
{cache, [
{ttl, 20}
,{n, 10}
,{policy, lru}
%,{size, 500000}
%,{memory, 400000000}
]}.

View File

@ -1,7 +1,7 @@
{application, cache,
[
{description, "in-memory cache"},
{vsn, "0.1.0"},
{vsn, "0.2.0"},
{modules, [
cache,
cache_sup,

View File

@ -1,49 +1,50 @@
-module(cache).
-export([start/0]).
-export([start_link/2, i/1, put/3, get/2, evict/1, stop/1]).
start() ->
AppFile = code:where_is_file(atom_to_list(?MODULE) ++ ".app"),
{ok, [{application, _, List}]} = file:consult(AppFile),
Apps = proplists:get_value(applications, List, []),
lists:foreach(
fun(X) ->
ok = case application:start(X) of
{error, {already_started, X}} -> ok;
Ret -> Ret
end
end,
Apps
),
application:start(?MODULE).
-export([
start_link/2, drop/1, i/1,
put/3, put_/3, get/2, has/2, remove/2, remove_/2
]).
%%
%%
start_link(Name, Opts) ->
cache_bucket:start_link(Name, Opts).
start_link(Cache, Opts) ->
cache_bucket:start_link(Cache, Opts).
%%
%%
drop(Cache) ->
erlang:exit(whereis(Cache), shutdown).
%%
%%
i(Cache) ->
cache_bucket:i(Cache).
gen_server:call(Cache, i).
%%
%%
put(Cache, Key, Val) ->
cache_bucket:put(Cache, Key, Val).
gen_server:call(Cache, {put, Key, Val}).
put_(Cache, Key, Val) ->
gen_server:cast(Cache, {put, Key, Val}).
%%
%%
get(Cache, Key) ->
cache_bucket:get(Cache, Key).
gen_server:call(Cache, {get, Key}).
%%
%%
evict(Cache) ->
cache_bucket:evict(Cache).
has(Cache, Key) ->
gen_server:call(Cache, {has, Key}).
%%
%%
stop(Cache) ->
cache_bucket:stop(Cache).
remove(Cache, Key) ->
gen_server:call(Cache, {remove, Key}).
%%
%%
remove_(Cache, Key) ->
gen_server:cast(Cache, {remove, Key}).

19
src/cache.hrl Normal file
View File

@ -0,0 +1,19 @@
%-define(VERBOSE, true).
-ifdef(VERBOSE).
-define(DEBUG(Str, Args), error_logger:error_msg(Str, Args)).
-else.
-define(DEBUG(Str, Args), ok).
-endif.
%% default cache eviction policy
-define(DEF_CACHE_POLICY, lru).
%% default cache ttl and number of generations
-define(DEF_CACHE_TTL, 600).
-define(DEF_CACHE_N, 10).
%% default cache house keeping frequency
-define(DEF_CACHE_QUOTA, 5000).

66
src/cache_benchmark.erl Normal file
View File

@ -0,0 +1,66 @@
%% @description
%% basho_bench driver
-module(cache_benchmark).
-export([
new/1, run/4
]).
%%
%%
new(_Id) ->
try
lager:set_loglevel(lager_console_backend, basho_bench_config:get(log_level, info)),
init()
catch _:Err ->
error_logger:error_msg("cache failed: ~p", [Err]),
halt(1)
end,
{ok, undefined}.
%%
%%
run(put, KeyGen, ValGen, S) ->
Key = KeyGen(),
case (catch cache:put(cache, Key, ValGen())) of
ok -> {ok, S};
E -> {error, failure(p, Key, E), S}
end;
run(get, KeyGen, _ValueGen, S) ->
Key = KeyGen(),
case (catch cache:get(cache, Key)) of
Val when is_binary(Val) -> {ok, S};
undefined -> {ok, S};
E -> {error, failure(g, Key, E), S}
end;
run(remove, KeyGen, _ValueGen, S) ->
Key = KeyGen(),
case (catch cache:remove(cache, Key)) of
ok -> {ok, S};
E -> {error, failure(r, Key, E), S}
end;
run(dump, _KeyGen, _ValueGen, S) ->
error_logger:info_msg("cache: ~p", [cache:i(cache)]),
{ok, S}.
%%
%%
init() ->
case application:start(cache) of
{error, {already_started, _}} ->
ok;
ok ->
Cache = basho_bench_config:get(cache, 30000),
{ok, _} = cache:start_link(cache, Cache)
end.
%%
%%
failure(Tag, _Key, E) ->
%io:format("----> ~p~n", [process_info(pns:whereis(kv, Key))]),
io:format("~s -> ~p~n", [Tag, E]),
failed.

View File

@ -2,97 +2,73 @@
-module(cache_bucket).
-behaviour(gen_server).
-author('Dmitry Kolesnikov <dmkolesnikov@gmail.com>').
-include("cache.hrl").
-export([start_link/2]).
-export([i/1, put/3, get/2, evict/1, stop/1]).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
-define(DEFAULT_POLICY, lru).
-define(DEFAULT_TTL, 60000).
-define(DEFAULT_EVICT, 10000).
-define(DEFAULT_CHUNK, 100).
-export([
start_link/2,
init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3
]).
%%
%%
-record(cache, {
policy, %%
memory, %% memory threshold
size, %% size threshold
chunk, %% number of evicted elements
name :: atom(), %% name of cache bucket
heap :: list(), %% heap
ttl, %% cache element ttl
evict, %% house keeping timer to evict cache elements
elements,
access,
n = ?DEF_CACHE_N :: integer(), %% number of cells
ttl = ?DEF_CACHE_TTL :: integer(), %% chunk time to live
policy = ?DEF_CACHE_POLICY :: integer(), %% eviction policy
evict :: integer(), %% age of heap cell (cell eviction frequency)
hit,
miss
quota = ?DEF_CACHE_QUOTA :: integer(), %% frequency of limit enforcement (clean-up)
quota_size :: integer(), %% max number of elements
quota_memory :: integer() %% max number of memory in bytes
}).
%%
%%
start_link(Name, Opts) ->
gen_server:start_link({local, Name}, ?MODULE, [Opts], []).
gen_server:start_link({local, Name}, ?MODULE, [Name, Opts], []).
init([Opts]) ->
init(Opts, #cache{
evict = ?DEFAULT_EVICT,
policy = ?DEFAULT_POLICY,
chunk = ?DEFAULT_CHUNK,
ttl = ?DEFAULT_TTL * 1000,
hit = 0,
miss = 0
}).
init([Name, Opts]) ->
{ok, init(Opts, #cache{name=Name})}.
init([{policy, X} | T], #cache{}=S) ->
init(T, S#cache{policy=X});
init([{memory, X} | T], #cache{}=S) ->
init(T, S#cache{memory=X div erlang:system_info(wordsize)});
init([{memory, X} | Opts], S) ->
init(Opts, S#cache{quota_memory=X div erlang:system_info(wordsize)});
init([{size, X} | T], #cache{}=S) ->
init(T, S#cache{size=X});
init([{size, X} | Opts], S) ->
init(Opts, S#cache{quota_size=X});
init([{chunk, X} | T], #cache{}=S) ->
init(T, S#cache{chunk=X});
init([{n, X} | Opts], S) ->
init(Opts, S#cache{n = X});
init([{ttl, X} | T], #cache{}=S) ->
init(T, S#cache{ttl=X * 1000});
init([{ttl, X} | Opts], S) ->
init(Opts, S#cache{ttl = X});
init([{evict, X} | T], #cache{}=S) ->
init(T, S#cache{evict=X});
init([{quota, X} | Opts], S) ->
init(Opts, S#cache{quota=X * 1000});
init([], #cache{evict=Evict}=S) ->
init([_ | Opts], S) ->
init(Opts, S);
init([], S) ->
random:seed(erlang:now()),
Evict = (S#cache.ttl div S#cache.n) * 1000,
erlang:send_after(S#cache.quota, self(), quota),
erlang:send_after(Evict, self(), evict),
{ok,
S#cache{
elements = ets:new(undefined, [set, private]),
access = ets:new(undefined, [ordered_set, private])
}
S#cache{
heap = cache_heap:new(S#cache.n),
evict = Evict
}.
%%%----------------------------------------------------------------------------
%%%
%%% api
%%%
%%%----------------------------------------------------------------------------
%%
%%
i(Cache) ->
gen_server:call(Cache, info).
put(Cache, Key, Val) ->
gen_server:cast(Cache, {put, Key, Val}).
get(Cache, Key) ->
gen_server:call(Cache, {get, Key}, infinity).
evict(Cache) ->
gen_server:cast(Cache, evict).
stop(Cache) ->
gen_server:call(Cache, stop, infinity).
%%
terminate(_Reason, S) ->
cache_heap:free(S#cache.heap),
ok.
%%%----------------------------------------------------------------------------
%%%
@ -100,72 +76,51 @@ stop(Cache) ->
%%%
%%%----------------------------------------------------------------------------
%%
%%
handle_call(info, _Tx, #cache{elements=E, access=A, hit=Hit, miss=Miss}=S) ->
Mem = ets:info(E, memory) + ets:info(A, memory),
Size = ets:info(E, size),
Info = [
{memory, Mem},
{size, Size},
{hit, Hit},
{miss, Miss}
],
{reply, {ok, Info}, S};
handle_call({put, Key, Val}, _, S) ->
{reply, ok, insert(Key, Val, S)};
handle_call({get, Key}, _Tx, #cache{ttl=TTL, elements=E, access=A, hit=Hit, miss=Miss}=S) ->
Now = usec(),
case ets:lookup(E, Key) of
[] ->
{reply, none, S#cache{miss=Miss + 1}};
[{Key, _Val, Expire0}] when Expire0 =< Now ->
{reply, none, S#cache{miss=Miss + 1}};
[{Key, Val, Expire0}] ->
Expire = Now + TTL,
ets:insert(E, {Key, Val, Expire}),
ets:delete(A, Expire0),
ets:insert(A, {Expire, Key}),
{reply, {ok, Val}, S#cache{hit=Hit + 1}}
handle_call({get, Key}, _, S) ->
{reply, lookup(Key, S), S};
handle_call({has, Key}, _, S) ->
case member(Key, S) of
true -> {reply, true, S};
_ -> {reply, false, S}
end;
handle_call(stop, _Tx, S) ->
{stop, normal, ok, S};
handle_call({remove, Key}, _, S) ->
{reply, ok, remove(Key, S)};
handle_call(_Req, _Tx, S) ->
{reply, {error, not_implemented}, S}.
handle_call(i, _, S) ->
Cells = cache_heap:cells(S#cache.heap),
Size = cache_heap:size(S#cache.heap),
Memory = cache_heap:memory(S#cache.heap),
{reply, [{heap, Cells}, {size, Size}, {memory, Memory}], S};
%%
%%
handle_cast({put, Key, Val}, #cache{ttl=TTL, elements=E, access=A}=S) ->
Expire = usec() + TTL,
ets:insert(E, {Key, Val, Expire}),
ets:insert(A, {Expire, Key}),
{noreply, S};
handle_cast(evict, #cache{elements=E, access=A}=S) ->
evict_expired(usec(), E, A),
evict_cache(S),
{noreply, S};
handle_cast(_Req, S) ->
handle_call(_, _, S) ->
{noreply, S}.
%%
%%
handle_info(evict, #cache{evict=Evict, elements=E, access=A}=S) ->
evict_expired(usec(), E, A),
evict_cache(S),
erlang:send_after(Evict, self(), evict),
{noreply, S};
handle_cast({put, Key, Val}, S) ->
{noreply, insert(Key, Val, S)};
handle_cast({remove, Key}, S) ->
{noreply, remove(Key, S)};
handle_info(_Msg, S) ->
handle_cast(_, S) ->
{noreply, S}.
%%
%%
terminate(_Reason, _S) ->
ok.
handle_info(evict, S) ->
erlang:send_after(S#cache.evict, self(), evict),
{noreply, evict(S)};
handle_info(quota, S) ->
erlang:send_after(S#cache.quota, self(), quota),
{noreply, quota(S)};
handle_info(_, S) ->
{noreply, S}.
%%
%%
@ -180,106 +135,143 @@ code_change(_Vsn, S, _Extra) ->
%%%----------------------------------------------------------------------------
%%
%%
usec() ->
{Mega, Sec, USec} = erlang:now(),
(Mega * 1000000 + Sec) * 1000000 + USec.
%% evict key from cells
evict_key(Key, List) ->
dowhile(
fun(Cell) ->
case ets:member(Cell, Key) of
true ->
ets:delete(Cell, Key),
Cell;
Result ->
Result
end
end,
List
).
%%
%%
check_memory(#cache{memory=undefined}) ->
true;
check_memory(#cache{memory=Mem, elements=E, access=A}) ->
Used = ets:info(E, memory) + ets:info(A, memory),
if
Used > Mem -> false;
true -> true
insert(Key, Val, #cache{}=S) ->
[Head | Tail] = cache_heap:cells(S#cache.heap),
true = ets:insert(Head, {Key, Val}),
_ = evict_key(Key, Tail),
?DEBUG("cache ~p: put ~p to cell ~p~n", [S#cache.name, Key, Head]),
S.
%%
%%
remove(Key, S) ->
Cell = evict_key(Key, cache_heap:cells(S#cache.heap)),
?DEBUG("cache ~p: remove ~p in cell ~p~n", [S#cache.name, Key, Cell]),
S.
%%
%%
member(Key, S) ->
dowhile(
fun(X) -> ets:member(X, Key) end,
cache_heap:cells(S#cache.heap)
).
%%
%%
lookup(Key, #cache{policy=mru}=S) ->
% cache always evicts last generation
% MRU caches do not prolong recently used entity
dowhile(
fun(Cell) ->
case ets:lookup(Cell, Key) of
[] ->
false;
[{_, Val}] ->
?DEBUG("cache ~p: get ~p at cell ~p~n", [S#cache.name, Key, Cell]),
Val
end
end,
lists:reverse(cache_heap:cells(S#cache.heap))
);
lookup(Key, S) ->
[Head | Tail] = cache_heap:cells(S#cache.heap),
case ets:lookup(Head, Key) of
% no value at head chunk, lookup and evict tail
[] ->
dowhile(
fun(Cell) ->
case ets:lookup(Cell, Key) of
[] ->
false;
[{_, Val}] ->
?DEBUG("cache ~p: get ~p at cell ~p~n", [S#cache.name, Key, Cell]),
_ = ets:delete(Cell, Key),
true = ets:insert(Head, {Key, Val}),
Val
end
end,
Tail
);
[{_, Val}] ->
?DEBUG("cache ~p: get ~p in cell ~p~n", [S#cache.name, Key, Head]),
Val
end.
%%
%%
check_size(#cache{size=undefined}) ->
true;
check_size(#cache{size=Size, elements=E}) ->
Used = ets:info(E, size),
if
Used > Size -> false;
true -> true
end.
%%
%%
check(S) ->
case check_memory(S) of
true -> check_size(S);
false -> false
end.
%%
%%
evict_expired(Expire, Element, Access) ->
case ets:first(Access) of
'$end_of_table' ->
ok;
Time when Time > Expire ->
ok;
Time ->
[{_, Key}] = ets:lookup(Access, Time),
ets:delete(Element, Key),
ets:delete(Access, Time),
evict_expired(Expire, Element, Access)
end.
%%
%%
evict_cache(#cache{policy=lru, chunk=Chunk, elements=E, access=A}=S) ->
case check(S) of
true ->
ok;
false ->
N = erlang:max(1, random:uniform(Chunk)),
evict_lru(N, E, A),
evict_cache(S)
%% execute predicate while it succeeded
dowhile(Pred, [Head | Tail]) ->
case Pred(Head) of
% predicate false, apply predicate to next chunk
false -> dowhile(Pred, Tail);
Result -> Result
end;
evict_cache(#cache{policy=mru, chunk=Chunk, elements=E, access=A}=S) ->
case check(S) of
true ->
ok;
false ->
N = erlang:max(1, random:uniform(Chunk)),
evict_mru(N, E, A),
evict_cache(S)
dowhile(_Pred, []) ->
undefined.
%%
%%
evict(#cache{}=S) ->
Cell = cache_heap:last(S#cache.heap),
?DEBUG("cache ~p: free cell ~p~n", [S#cache.name, Cell]),
S#cache{
heap = cache_heap:alloc(cache_heap:free(Cell, S#cache.heap))
}.
drop(#cache{}=S) ->
Cell = cache_heap:last(S#cache.heap),
?DEBUG("cache ~p: free cell ~p~n", [S#cache.name, Cell]),
S#cache{
heap = cache_heap:free(Cell, S#cache.heap)
}.
%%
%%
quota(#cache{}=S) ->
maybe_memory_quota(
maybe_size_quota(S)
).
maybe_size_quota(#cache{quota_size=undefined}=S) ->
S;
maybe_size_quota(S) ->
case lists:sum(cache_heap:size(S#cache.heap)) of
Size when Size > S#cache.quota_size ->
maybe_size_quota(drop(S));
_ ->
S#cache{
heap = cache_heap:talloc(S#cache.n, S#cache.heap)
}
end.
%%
%%
evict_lru(0, _Element, _Access) ->
ok;
evict_lru(N, Element, Access) ->
case ets:first(Access) of
'$end_of_table' ->
ok;
Time ->
[{_, Key}] = ets:lookup(Access, Time),
ets:delete(Element, Key),
ets:delete(Access, Time),
evict_lru(N - 1, Element, Access)
maybe_memory_quota(#cache{quota_memory=undefined}=S) ->
S;
maybe_memory_quota(S) ->
case lists:sum(cache_heap:memory(S#cache.heap)) of
Size when Size > S#cache.quota_memory ->
maybe_memory_quota(drop(S));
_ ->
S#cache{
heap = cache_heap:talloc(S#cache.n, S#cache.heap)
}
end.
%%
%%
evict_mru(0, _Element, _Access) ->
ok;
evict_mru(N, Element, Access) ->
case ets:last(Access) of
'$end_of_table' ->
ok;
Time ->
[{_, Key}] = ets:lookup(Access, Time),
ets:delete(Element, Key),
ets:delete(Access, Time),
evict_mru(N - 1, Element, Access)
end.

97
src/cache_heap.erl Normal file
View File

@ -0,0 +1,97 @@
%%
%% Copyright (c) 2012, Dmitry Kolesnikov
%% All Rights Reserved.
%%
%% This library is free software; you can redistribute it and/or modify
%% it under the terms of the GNU Lesser General Public License, version 3.0
%% as published by the Free Software Foundation (the "License").
%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
%%
%% You should have received a copy of the GNU Lesser General Public
%% License along with this library; if not, write to the Free Software
%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
%% USA or retrieve online http://www.opensource.org/licenses/lgpl-3.0.html
%%
%% @description
%% heap of ETS entities (sorted by age)
-module(cache_heap).
-export([
new/1, alloc/1, talloc/2, free/1, free/2,
cells/1, size/1, memory/1, last/1, first/1
]).
-record(heap, {
cells :: list() %% list of cells
}).
%%
%% create new heap
new(N) ->
lists:foldl(
fun(_, Acc) -> alloc(Acc) end,
#heap{cells=[]},
lists:seq(1, N)
).
%%
%% allocate new cell
alloc(#heap{}=H) ->
H#heap{
cells = [create() | H#heap.cells]
}.
% tail alloc
talloc(N, #heap{}=H)
when length(H#heap.cells) < N ->
talloc(N,
H#heap{
cells = H#heap.cells ++ [create()]
}
);
talloc(_, #heap{}=H) ->
H.
create() ->
ets:new(undefined, [set, protected]).
%%
%% free cells
free(#heap{cells=Cells}) ->
[ets:delete(X) || {_, X} <- Cells],
ok.
free(Cell, #heap{}=H) ->
ets:delete(Cell),
H#heap{
cells = lists:delete(Cell, H#heap.cells)
}.
%%
%%
cells(#heap{}=H) ->
H#heap.cells.
%%
%%
size(#heap{}=H) ->
[ets:info(X, size) || X <- H#heap.cells].
%%
%%
memory(#heap{}=H) ->
[ets:info(X, memory) || X <- H#heap.cells].
%%
%%
last(#heap{}=H) ->
lists:last(H#heap.cells).
%%
%%
first(#heap{}=H) ->
hd(H#heap.cells).

View File

@ -1,14 +1,15 @@
%%
-module(cache_sup).
-behaviour(supervisor).
-author('Dmitry Kolesnikov <dmkolesnikov@gmail.com>').
-export([start_link/0, init/1]).
%%
%%
start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
{ok, Sup} = supervisor:start_link({local, ?MODULE}, ?MODULE, []),
lists:foreach(fun default_cache/1, default()),
{ok, Sup}.
init([]) ->
{ok,
@ -16,4 +17,19 @@ init([]) ->
{one_for_one, 4, 1800},
[]
}
}.
}.
%% list of default caches
default() ->
case application:get_env(cache, default) of
{ok, Val} -> Val;
undefined -> []
end.
default_cache({Name, Opts}) ->
supervisor:start_child(?MODULE, {
Name,
{cache, start_link, [Name, Opts]},
permanent, 900000, worker, dynamic
}).

View File

@ -2,132 +2,170 @@
-author('Dmitry Kolesnikov <dmkolesnikov@gmail.com>').
-include_lib("eunit/include/eunit.hrl").
-define(CACHE, [
{ttl, 3}, %% time-to-live 3 sec
{n, 3}, %% 3 cells
{evict, 1} %% evict 1 sec
]).
lifecyle_1_test() ->
cache:start(),
{ok, _} = cache:start_link(test, [
{ttl, 10},
{evict, 5}
]),
ok = cache:put(test, key, val),
timer:sleep(6),
{ok, val} = cache:get(test, key),
timer:sleep(6),
{ok, val} = cache:get(test, key),
timer:sleep(20),
none = cache:get(test, key),
cache:stop(test).
lru_test_() ->
{
setup,
fun cache_init/0,
fun cache_free/1,
[
{"put", fun cache_put/0}
,{"has", fun cache_has/0}
,{"get", fun cache_get/0}
,{"del", fun cache_del/0}
,{"lifecycle 1", {timeout, 10000, fun cache_lc1/0}}
]
}.
cache_init() ->
cache:start_link(test, ?CACHE).
cache_free({ok, Pid}) ->
erlang:unlink(Pid),
cache:drop(test).
cache_put() ->
ok = cache:put(test, <<"key">>, <<"val">>).
cache_has() ->
true = cache:has(test, <<"key">>),
false = cache:has(test, <<"yek">>).
cache_get() ->
<<"val">> = cache:get(test, <<"key">>),
undefined = cache:get(test, <<"yek">>).
cache_del() ->
ok = cache:remove(test, <<"key">>),
ok = cache:remove(test, <<"yek">>).
cache_lc1() ->
error_logger:error_msg("~n~n life-cycle #1"),
ok = cache:put(test, key, val),
timer:sleep(1200),
val = cache:get(test, key),
timer:sleep(1200),
val = cache:get(test, key),
timer:sleep(1200),
val = cache:get(test, key),
timer:sleep(3200),
undefined = cache:get(test, key).
lifecyle_2_test() ->
cache:start(),
{ok, _} = cache:start_link(test, [
{ttl, 10},
{evict, 100}
]),
ok = cache:put(test, key, val),
timer:sleep(6),
{ok, val} = cache:get(test, key),
timer:sleep(6),
{ok, val} = cache:get(test, key),
timer:sleep(20),
none = cache:get(test, key),
cache:stop(test).
% lifecyle_2_test() ->
% cache:start(),
% {ok, _} = cache:start_link(test, [
% {ttl, 10},
% {evict, 100}
% ]),
% ok = cache:put(test, key, val),
% timer:sleep(6),
% {ok, val} = cache:get(test, key),
% timer:sleep(6),
% {ok, val} = cache:get(test, key),
% timer:sleep(20),
% none = cache:get(test, key),
% cache:stop(test).
lifecyle_3_test() ->
cache:start(),
{ok, _} = cache:start_link(test, [
{ttl, 10},
{evict, 5}
]),
ok = cache:put(test, key1, val1),
timer:sleep(5),
ok = cache:put(test, key2, val2),
timer:sleep(5),
ok = cache:put(test, key3, val3),
timer:sleep(5),
ok = cache:put(test, key4, val4),
% lifecyle_3_test() ->
% cache:start(),
% {ok, _} = cache:start_link(test, [
% {ttl, 10},
% {evict, 5}
% ]),
% ok = cache:put(test, key1, val1),
% timer:sleep(5),
% ok = cache:put(test, key2, val2),
% timer:sleep(5),
% ok = cache:put(test, key3, val3),
% timer:sleep(5),
% ok = cache:put(test, key4, val4),
none = cache:get(test, key1),
none = cache:get(test, key2),
{ok, val3} = cache:get(test, key3),
{ok, val4} = cache:get(test, key4),
cache:stop(test).
% none = cache:get(test, key1),
% none = cache:get(test, key2),
% {ok, val3} = cache:get(test, key3),
% {ok, val4} = cache:get(test, key4),
% cache:stop(test).
evict_lru_1_test() ->
cache:start(),
{ok, _} = cache:start_link(test, [
{policy, lru},
{ttl, 100},
{evict, 5},
{size, 10},
{chunk, 2}
]),
lists:foreach(
fun(X) -> cache:put(test, X, X) end,
lists:seq(1, 10)
),
timer:sleep(10),
{ok, 1} = cache:get(test, 1),
cache:put(test, key, val),
timer:sleep(10),
none = cache:get(test, 2),
cache:stop(test).
% evict_lru_1_test() ->
% cache:start(),
% {ok, _} = cache:start_link(test, [
% {policy, lru},
% {ttl, 100},
% {evict, 5},
% {size, 10},
% {chunk, 2}
% ]),
% lists:foreach(
% fun(X) -> cache:put(test, X, X) end,
% lists:seq(1, 10)
% ),
% timer:sleep(10),
% {ok, 1} = cache:get(test, 1),
% cache:put(test, key, val),
% timer:sleep(10),
% none = cache:get(test, 2),
% cache:stop(test).
evict_lru_2_test() ->
cache:start(),
{ok, _} = cache:start_link(test, [
{policy, lru},
{ttl, 100},
{evict, 100},
{size, 10},
{chunk, 2}
]),
lists:foreach(
fun(X) -> cache:put(test, X, X) end,
lists:seq(1, 10)
),
{ok, 1} = cache:get(test, 1),
cache:put(test, key, val),
cache:evict(test),
none = cache:get(test, 2),
cache:stop(test).
% evict_lru_2_test() ->
% cache:start(),
% {ok, _} = cache:start_link(test, [
% {policy, lru},
% {ttl, 100},
% {evict, 100},
% {size, 10},
% {chunk, 2}
% ]),
% lists:foreach(
% fun(X) -> cache:put(test, X, X) end,
% lists:seq(1, 10)
% ),
% {ok, 1} = cache:get(test, 1),
% cache:put(test, key, val),
% cache:evict(test),
% none = cache:get(test, 2),
% cache:stop(test).
evict_mru_1_test() ->
cache:start(),
{ok, _} = cache:start_link(test, [
{policy, mru},
{ttl, 100},
{evict, 5},
{size, 10},
{chunk, 2}
]),
lists:foreach(
fun(X) -> cache:put(test, X, X) end,
lists:seq(1, 10)
),
timer:sleep(10),
{ok, 1} = cache:get(test, 1),
cache:put(test, key, val),
timer:sleep(10),
none = cache:get(test, key),
cache:stop(test).
% evict_mru_1_test() ->
% cache:start(),
% {ok, _} = cache:start_link(test, [
% {policy, mru},
% {ttl, 100},
% {evict, 5},
% {size, 10},
% {chunk, 2}
% ]),
% lists:foreach(
% fun(X) -> cache:put(test, X, X) end,
% lists:seq(1, 10)
% ),
% timer:sleep(10),
% {ok, 1} = cache:get(test, 1),
% cache:put(test, key, val),
% timer:sleep(10),
% none = cache:get(test, key),
% cache:stop(test).
evict_mru_2_test() ->
cache:start(),
{ok, _} = cache:start_link(test, [
{policy, mru},
{ttl, 100},
{evict, 100},
{size, 10},
{chunk, 2}
]),
lists:foreach(
fun(X) -> cache:put(test, X, X) end,
lists:seq(1, 10)
),
{ok, 1} = cache:get(test, 1),
cache:put(test, key, val),
cache:evict(test),
none = cache:get(test, key),
cache:stop(test).
% evict_mru_2_test() ->
% cache:start(),
% {ok, _} = cache:start_link(test, [
% {policy, mru},
% {ttl, 100},
% {evict, 100},
% {size, 10},
% {chunk, 2}
% ]),
% lists:foreach(
% fun(X) -> cache:put(test, X, X) end,
% lists:seq(1, 10)
% ),
% {ok, 1} = cache:get(test, 1),
% cache:put(test, key, val),
% cache:evict(test),
% none = cache:get(test, key),
% cache:stop(test).