mirror of
https://github.com/valitydev/cache.git
synced 2024-11-06 01:45:19 +00:00
extra function calls to lookup data and meta-data
This commit is contained in:
parent
cfdba43817
commit
dfcf699149
@ -17,7 +17,7 @@
|
|||||||
%,{remove, 1}
|
%,{remove, 1}
|
||||||
]}.
|
]}.
|
||||||
|
|
||||||
{cache, {mycache, 'cache@127.0.0.1'}}.
|
%{cache, {mycache, 'cache@127.0.0.1'}}.
|
||||||
{local, [
|
{local, [
|
||||||
{ttl, 20}
|
{ttl, 20}
|
||||||
,{n, 10}
|
,{n, 10}
|
||||||
|
@ -1,2 +1,3 @@
|
|||||||
{cover_enabled, true}.
|
{cover_enabled, true}.
|
||||||
|
|
||||||
|
{deps, []}.
|
||||||
|
103
src/cache.erl
103
src/cache.erl
@ -1,57 +1,130 @@
|
|||||||
|
%%
|
||||||
|
%% 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
|
||||||
|
%% segmented in-memory cache, cache memory is split into N segments,
|
||||||
|
%% each segment is associated with quota and ttl, eviction policy applied
|
||||||
|
%% at segment level.
|
||||||
-module(cache).
|
-module(cache).
|
||||||
|
-include("cache.hrl").
|
||||||
|
|
||||||
-export([
|
-export([
|
||||||
start_link/2, drop/1, i/1,
|
start_link/2,
|
||||||
put/3, put/4, put_/3, put_/4,
|
drop/1,
|
||||||
get/2, has/2,
|
i/1,
|
||||||
|
heap/2,
|
||||||
|
put/3, put/4, put_/3, put_/4,
|
||||||
|
get/2, lookup/2,
|
||||||
|
has/2, ttl/2,
|
||||||
remove/2, remove_/2
|
remove/2, remove_/2
|
||||||
]).
|
]).
|
||||||
|
|
||||||
|
-type(key() :: any()).
|
||||||
|
-type(entity() :: any()).
|
||||||
|
|
||||||
%%
|
%%
|
||||||
%%
|
%% start new cache
|
||||||
|
-spec(start_link/2 :: (atom(), list()) -> {ok, pid()} | {error, any()}).
|
||||||
|
|
||||||
start_link(Cache, Opts) ->
|
start_link(Cache, Opts) ->
|
||||||
cache_bucket:start_link(Cache, Opts).
|
cache_bucket:start_link(Cache, Opts).
|
||||||
|
|
||||||
%%
|
%%
|
||||||
%%
|
%% drop cache
|
||||||
|
-spec(drop/1 :: (atom()) -> ok).
|
||||||
|
|
||||||
drop(Cache) ->
|
drop(Cache) ->
|
||||||
erlang:exit(whereis(Cache), shutdown).
|
erlang:exit(whereis(Cache), shutdown).
|
||||||
|
|
||||||
%%
|
%%
|
||||||
%%
|
%% return cache meta data
|
||||||
|
-spec(i/1 :: (atom()) -> list()).
|
||||||
|
|
||||||
i(Cache) ->
|
i(Cache) ->
|
||||||
gen_server:call(Cache, i).
|
gen_server:call(Cache, i).
|
||||||
|
|
||||||
%%
|
%%
|
||||||
|
%% return nth cache heap
|
||||||
|
-spec(heap/2 :: (atom(), integer()) -> integer() | badarg).
|
||||||
|
|
||||||
|
heap(Cache, N) ->
|
||||||
|
gen_server:call(Cache, {heap, N}).
|
||||||
|
|
||||||
|
|
||||||
%%
|
%%
|
||||||
|
%% put entity to cache
|
||||||
|
-spec(put/3 :: (atom(), key(), entity()) -> ok).
|
||||||
|
-spec(put_/3 :: (atom(), key(), entity()) -> ok).
|
||||||
|
-spec(put/4 :: (atom(), key(), entity(), integer()) -> ok).
|
||||||
|
-spec(put_/4 :: (atom(), key(), entity(), integer()) -> ok).
|
||||||
|
|
||||||
put(Cache, Key, Val) ->
|
put(Cache, Key, Val) ->
|
||||||
gen_server:call(Cache, {put, Key, Val}).
|
gen_server:call(Cache, {put, Key, Val}, ?DEF_IO_TIMEOUT).
|
||||||
put(Cache, Key, Val, TTL) ->
|
put(Cache, Key, Val, TTL) ->
|
||||||
gen_server:call(Cache, {put, Key, Val, TTL}).
|
gen_server:call(Cache, {put, Key, Val, TTL}, ?DEF_IO_TIMEOUT).
|
||||||
|
|
||||||
put_(Cache, Key, Val) ->
|
put_(Cache, Key, Val) ->
|
||||||
gen_server:cast(Cache, {put, Key, Val}).
|
gen_server:cast(Cache, {put, Key, Val}).
|
||||||
|
|
||||||
put_(Cache, Key, Val, TTL) ->
|
put_(Cache, Key, Val, TTL) ->
|
||||||
gen_server:cast(Cache, {put, Key, Val, TTL}).
|
gen_server:cast(Cache, {put, Key, Val, TTL}).
|
||||||
|
|
||||||
%%
|
%%
|
||||||
%%
|
%% get entity from cache, prolong ttl
|
||||||
|
-spec(get/2 :: (atom(), key()) -> entity() | undefined).
|
||||||
|
|
||||||
get(Cache, Key) ->
|
get(Cache, Key) ->
|
||||||
gen_server:call(Cache, {get, Key}).
|
gen_server:call(Cache, {get, Key}, ?DEF_IO_TIMEOUT).
|
||||||
|
|
||||||
%%
|
%%
|
||||||
|
%% lookup entity at cache do not prolong ttl
|
||||||
|
-spec(lookup/2 :: (atom(), key()) -> entity() | undefined).
|
||||||
|
|
||||||
|
lookup(Cache, Key) ->
|
||||||
|
gen_server:call(Cache, {lookup, Key}, ?DEF_IO_TIMEOUT).
|
||||||
|
|
||||||
%%
|
%%
|
||||||
|
%% check entity at cache
|
||||||
|
-spec(has/2 :: (atom(), key()) -> true | false).
|
||||||
|
|
||||||
has(Cache, Key) ->
|
has(Cache, Key) ->
|
||||||
gen_server:call(Cache, {has, Key}).
|
gen_server:call(Cache, {has, Key}, ?DEF_IO_TIMEOUT).
|
||||||
|
|
||||||
%%
|
%%
|
||||||
|
%% check entity at cache and return estimated ttl
|
||||||
|
-spec(ttl/2 :: (atom(), key()) -> true | false).
|
||||||
|
|
||||||
|
ttl(Cache, Key) ->
|
||||||
|
gen_server:call(Cache, {ttl, Key}, ?DEF_IO_TIMEOUT).
|
||||||
|
|
||||||
%%
|
%%
|
||||||
|
%% remove entity from cache
|
||||||
|
-spec(remove/2 :: (atom(), key()) -> ok).
|
||||||
|
-spec(remove_/2 :: (atom(), key()) -> ok).
|
||||||
|
|
||||||
remove(Cache, Key) ->
|
remove(Cache, Key) ->
|
||||||
gen_server:call(Cache, {remove, Key}).
|
gen_server:call(Cache, {remove, Key}, ?DEF_IO_TIMEOUT).
|
||||||
|
|
||||||
%%
|
|
||||||
%%
|
|
||||||
remove_(Cache, Key) ->
|
remove_(Cache, Key) ->
|
||||||
gen_server:cast(Cache, {remove, Key}).
|
gen_server:cast(Cache, {remove, Key}).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -17,3 +17,5 @@
|
|||||||
|
|
||||||
%% default cache house keeping frequency
|
%% default cache house keeping frequency
|
||||||
-define(DEF_CACHE_QUOTA, 5).
|
-define(DEF_CACHE_QUOTA, 5).
|
||||||
|
|
||||||
|
-define(DEF_IO_TIMEOUT,60000).
|
@ -1,4 +1,22 @@
|
|||||||
%%
|
%%
|
||||||
|
%% 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
|
||||||
-module(cache_app).
|
-module(cache_app).
|
||||||
-behaviour(application).
|
-behaviour(application).
|
||||||
-author('Dmitry Kolesnikov <dmkolesnikov@gmail.com>').
|
-author('Dmitry Kolesnikov <dmkolesnikov@gmail.com>').
|
||||||
|
@ -1,3 +1,21 @@
|
|||||||
|
%%
|
||||||
|
%% 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
|
%% @description
|
||||||
%% basho_bench driver
|
%% basho_bench driver
|
||||||
-module(cache_benchmark).
|
-module(cache_benchmark).
|
||||||
|
@ -1,4 +1,23 @@
|
|||||||
%%
|
%%
|
||||||
|
%% 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
|
||||||
|
%%
|
||||||
-module(cache_bucket).
|
-module(cache_bucket).
|
||||||
-behaviour(gen_server).
|
-behaviour(gen_server).
|
||||||
-author('Dmitry Kolesnikov <dmkolesnikov@gmail.com>').
|
-author('Dmitry Kolesnikov <dmkolesnikov@gmail.com>').
|
||||||
@ -12,18 +31,27 @@
|
|||||||
%%
|
%%
|
||||||
%%
|
%%
|
||||||
-record(cache, {
|
-record(cache, {
|
||||||
name :: atom(), %% name of cache bucket
|
name :: atom(), %% name of cache bucket
|
||||||
heap :: list(), %% heap
|
heap = [] :: list(), %% cache heap segments
|
||||||
|
|
||||||
n = ?DEF_CACHE_N :: integer(), %% number of cells
|
n = ?DEF_CACHE_N :: integer(), %% number of segments
|
||||||
ttl = ?DEF_CACHE_TTL :: integer(), %% chunk time to live
|
ttl = ?DEF_CACHE_TTL :: integer(), %% chunk time to live
|
||||||
policy = ?DEF_CACHE_POLICY :: integer(), %% eviction policy
|
policy = ?DEF_CACHE_POLICY :: integer(), %% eviction policy
|
||||||
|
|
||||||
quota = ?DEF_CACHE_QUOTA :: integer(), %% frequency of limit enforcement (clean-up)
|
cardinality :: integer(), %% cache cardinality
|
||||||
quota_size :: integer(), %% max number of elements
|
memory :: integer(), %% cache memory limit
|
||||||
quota_memory :: integer(), %% max number of memory in bytes
|
|
||||||
|
|
||||||
counter :: any() %% counter functor
|
quota = ?DEF_CACHE_QUOTA :: integer(), %% quota enforcement timer
|
||||||
|
evict = undefined, %% evict timer
|
||||||
|
|
||||||
|
stats :: any() %% stats functor
|
||||||
|
}).
|
||||||
|
|
||||||
|
-record(heap, {
|
||||||
|
id :: integer(), %% segment heap
|
||||||
|
expire :: integer(), %% segment expire time
|
||||||
|
cardinality :: integer(), %% segment cardinality quota
|
||||||
|
memory :: integer() %% segment memory quota
|
||||||
}).
|
}).
|
||||||
|
|
||||||
%%
|
%%
|
||||||
@ -41,10 +69,10 @@ init([{policy, X} | T], #cache{}=S) ->
|
|||||||
init(T, S#cache{policy=X});
|
init(T, S#cache{policy=X});
|
||||||
|
|
||||||
init([{memory, X} | Opts], S) ->
|
init([{memory, X} | Opts], S) ->
|
||||||
init(Opts, S#cache{quota_memory=X});
|
init(Opts, S#cache{memory=X div erlang:sysinfo(wordsize)});
|
||||||
|
|
||||||
init([{size, X} | Opts], S) ->
|
init([{size, X} | Opts], S) ->
|
||||||
init(Opts, S#cache{quota_size=X});
|
init(Opts, S#cache{cardinality=X});
|
||||||
|
|
||||||
init([{n, X} | Opts], S) ->
|
init([{n, X} | Opts], S) ->
|
||||||
init(Opts, S#cache{n = X});
|
init(Opts, S#cache{n = X});
|
||||||
@ -55,24 +83,26 @@ init([{ttl, X} | Opts], S) ->
|
|||||||
init([{quota, X} | Opts], S) ->
|
init([{quota, X} | Opts], S) ->
|
||||||
init(Opts, S#cache{quota=X});
|
init(Opts, S#cache{quota=X});
|
||||||
|
|
||||||
init([{counter, X} | Opts], S) ->
|
init([{stats, X} | Opts], S) ->
|
||||||
init(Opts, S#cache{counter=X});
|
init(Opts, S#cache{stats=X});
|
||||||
|
|
||||||
init([_ | Opts], S) ->
|
init([_ | Opts], S) ->
|
||||||
init(Opts, S);
|
init(Opts, S);
|
||||||
|
|
||||||
init([], S) ->
|
init([], S) ->
|
||||||
random:seed(erlang:now()),
|
random:seed(erlang:now()),
|
||||||
set_quota_timeout(S),
|
Evict = cache_util:mdiv(S#cache.ttl, S#cache.n),
|
||||||
set_evict_timeout(S),
|
init_heap(
|
||||||
S#cache{
|
S#cache{
|
||||||
heap = cache_heap:new(S#cache.n)
|
evict = cache_util:timeout(Evict * 1000, evict),
|
||||||
}.
|
quota = cache_util:timeout(S#cache.quota * 1000, quota)
|
||||||
|
}
|
||||||
|
).
|
||||||
|
|
||||||
%%
|
%%
|
||||||
%%
|
%%
|
||||||
terminate(_Reason, S) ->
|
terminate(_Reason, _S) ->
|
||||||
cache_heap:free(S#cache.heap),
|
%cache_heap:free(S#cache.heap),
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
%%%----------------------------------------------------------------------------
|
%%%----------------------------------------------------------------------------
|
||||||
@ -82,35 +112,40 @@ terminate(_Reason, S) ->
|
|||||||
%%%----------------------------------------------------------------------------
|
%%%----------------------------------------------------------------------------
|
||||||
|
|
||||||
handle_call({put, Key, Val}, _, S) ->
|
handle_call({put, Key, Val}, _, S) ->
|
||||||
{reply, ok, insert(Key, Val, S)};
|
{reply, ok, cache_put(Key, Val, S)};
|
||||||
|
|
||||||
handle_call({put, Key, Val, _}, _, S) ->
|
handle_call({put, Key, Val, TTL}, _, S) ->
|
||||||
{reply, ok, insert(Key, Val, S)};
|
{reply, ok, cache_put(Key, Val, cache_util:now() + TTL, S)};
|
||||||
|
|
||||||
handle_call({get, Key}, _, S) ->
|
handle_call({get, Key}, _, S) ->
|
||||||
case lookup(Key, S) of
|
{reply, cache_get(Key, S), S};
|
||||||
undefined ->
|
|
||||||
inc_counter(miss, S),
|
handle_call({lookup, Key}, _, S) ->
|
||||||
{reply, undefined, S};
|
{reply, cache_lookup(Key, S), S};
|
||||||
Val ->
|
|
||||||
inc_counter(hit, S),
|
|
||||||
{reply, Val, S}
|
|
||||||
end;
|
|
||||||
|
|
||||||
handle_call({has, Key}, _, S) ->
|
handle_call({has, Key}, _, S) ->
|
||||||
case member(Key, S) of
|
{reply, cache_has(Key, S), S};
|
||||||
true -> {reply, true, S};
|
|
||||||
_ -> {reply, false, S}
|
handle_call({ttl, Key}, _, S) ->
|
||||||
end;
|
{reply, cache_ttl(Key, S), S};
|
||||||
|
|
||||||
handle_call({remove, Key}, _, S) ->
|
handle_call({remove, Key}, _, S) ->
|
||||||
{reply, ok, remove(Key, S)};
|
{reply, ok, cache_remove(Key, S)};
|
||||||
|
|
||||||
handle_call(i, _, S) ->
|
handle_call(i, _, S) ->
|
||||||
Cells = cache_heap:cells(S#cache.heap),
|
Heap = [X#heap.id || X <- S#cache.heap],
|
||||||
Size = cache_heap:size(S#cache.heap),
|
Expire = [X#heap.expire || X <- S#cache.heap],
|
||||||
Memory = cache_heap:memory(S#cache.heap),
|
Size = [ets:info(X#heap.id, size) || X <- S#cache.heap],
|
||||||
{reply, [{heap, Cells}, {size, Size}, {memory, Memory}], S};
|
Memory = [ets:info(X#heap.id, memory) || X <- S#cache.heap],
|
||||||
|
{reply, [{heap, Heap}, {expire, Expire}, {size, Size}, {memory, Memory}], S};
|
||||||
|
|
||||||
|
handle_call({heap, N}, _, S) ->
|
||||||
|
try
|
||||||
|
H = lists:nth(N, S#cache.heap),
|
||||||
|
{reply, H#heap.id, S}
|
||||||
|
catch _:_ ->
|
||||||
|
{reply, badarg, S}
|
||||||
|
end;
|
||||||
|
|
||||||
handle_call(_, _, S) ->
|
handle_call(_, _, S) ->
|
||||||
{noreply, S}.
|
{noreply, S}.
|
||||||
@ -118,24 +153,58 @@ handle_call(_, _, S) ->
|
|||||||
%%
|
%%
|
||||||
%%
|
%%
|
||||||
handle_cast({put, Key, Val}, S) ->
|
handle_cast({put, Key, Val}, S) ->
|
||||||
{noreply, insert(Key, Val, S)};
|
{noreply, cache_put(Key, Val, S)};
|
||||||
|
|
||||||
handle_cast({put, Key, Val, _}, S) ->
|
handle_cast({put, Key, Val, TTL}, S) ->
|
||||||
{noreply, insert(Key, Val, S)};
|
{noreply, cache_put(Key, Val, cache_util:now() + TTL, S)};
|
||||||
|
|
||||||
handle_cast({remove, Key}, S) ->
|
handle_cast({remove, Key}, S) ->
|
||||||
{noreply, remove(Key, S)};
|
{noreply, cache_remove(Key, S)};
|
||||||
|
|
||||||
handle_cast(_, S) ->
|
handle_cast(_, S) ->
|
||||||
{noreply, S}.
|
{noreply, S}.
|
||||||
|
|
||||||
handle_info(evict, S) ->
|
handle_info(evict, S) ->
|
||||||
set_evict_timeout(S),
|
Now = cache_util:now(),
|
||||||
{noreply, evict(S)};
|
case lists:last(S#cache.heap) of
|
||||||
|
H when H#heap.expire =< Now ->
|
||||||
|
{noreply,
|
||||||
|
free_heap(
|
||||||
|
S#cache{evict = cache_util:timeout(S#cache.evict, evict)}
|
||||||
|
)
|
||||||
|
};
|
||||||
|
_ ->
|
||||||
|
{noreply,
|
||||||
|
init_heap(
|
||||||
|
S#cache{evict = cache_util:timeout(S#cache.evict, evict)}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
end;
|
||||||
|
|
||||||
handle_info(quota, S) ->
|
handle_info(quota, S) ->
|
||||||
set_quota_timeout(S),
|
case is_heap_out_of_quota(hd(S#cache.heap)) of
|
||||||
{noreply, quota(S)};
|
true ->
|
||||||
|
case length(S#cache.heap) of
|
||||||
|
N when N =:= S#cache.n ->
|
||||||
|
{noreply,
|
||||||
|
free_heap(
|
||||||
|
S#cache{quota = cache_util:timeout(S#cache.evict, quota)}
|
||||||
|
)
|
||||||
|
};
|
||||||
|
_ ->
|
||||||
|
{noreply,
|
||||||
|
init_heap(
|
||||||
|
S#cache{quota = cache_util:timeout(S#cache.evict, quota)}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
end;
|
||||||
|
false ->
|
||||||
|
{noreply,
|
||||||
|
S#cache{
|
||||||
|
quota = cache_util:timeout(S#cache.quota, quota)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end;
|
||||||
|
|
||||||
handle_info(_, S) ->
|
handle_info(_, S) ->
|
||||||
{noreply, S}.
|
{noreply, S}.
|
||||||
@ -152,160 +221,184 @@ code_change(_Vsn, S, _Extra) ->
|
|||||||
%%%
|
%%%
|
||||||
%%%----------------------------------------------------------------------------
|
%%%----------------------------------------------------------------------------
|
||||||
|
|
||||||
%% set-up cache evict timeout
|
|
||||||
set_evict_timeout(#cache{ttl=undefined}) ->
|
|
||||||
ok;
|
|
||||||
set_evict_timeout(S) ->
|
|
||||||
T = (S#cache.ttl div S#cache.n) * 1000,
|
|
||||||
erlang:send_after(T, self(), evict).
|
|
||||||
|
|
||||||
set_quota_timeout(#cache{quota=undefined}) ->
|
|
||||||
ok;
|
|
||||||
set_quota_timeout(S) ->
|
|
||||||
T = S#cache.quota * 1000,
|
|
||||||
erlang:send_after(T, self(), quota).
|
|
||||||
|
|
||||||
%% update statistic
|
|
||||||
inc_counter(_Id, #cache{counter=undefined}) ->
|
|
||||||
ok;
|
|
||||||
inc_counter(Id, #cache{counter={M, F}, name=Name}) ->
|
|
||||||
M:F({cache, Name, Id});
|
|
||||||
inc_counter(Id, #cache{counter=Fun, name=Name}) ->
|
|
||||||
Fun({cache, Name, Id}).
|
|
||||||
|
|
||||||
|
|
||||||
%%
|
%%
|
||||||
%% evict key from cells
|
%% insert value to cache
|
||||||
evict_key(Key, List) ->
|
cache_put(Key, Val, #cache{}=S) ->
|
||||||
dowhile(
|
[Head | Tail] = S#cache.heap,
|
||||||
fun(Cell) ->
|
true = ets:insert(Head#heap.id, {Key, Val}),
|
||||||
case ets:member(Cell, Key) of
|
lists:foreach(
|
||||||
true ->
|
fun(X) -> ets:delete(X#heap.id, Key) end,
|
||||||
ets:delete(Cell, Key),
|
Tail
|
||||||
Cell;
|
),
|
||||||
Result ->
|
cache_util:stats(S#cache.stats, {cache, S#cache.name, put}),
|
||||||
Result
|
?DEBUG("cache ~p: put ~p to heap ~p~n", [S#cache.name, Key, Head#heap.id]),
|
||||||
end
|
|
||||||
end,
|
|
||||||
List
|
|
||||||
).
|
|
||||||
|
|
||||||
%%
|
|
||||||
%%
|
|
||||||
insert(Key, Val, #cache{}=S) ->
|
|
||||||
[Head | Tail] = cache_heap:cells(S#cache.heap),
|
|
||||||
true = ets:insert(Head, {Key, Val}),
|
|
||||||
_ = evict_key(Key, Tail),
|
|
||||||
inc_counter(put, S),
|
|
||||||
?DEBUG("cache ~p: put ~p to cell ~p~n", [S#cache.name, Key, Head]),
|
|
||||||
S.
|
S.
|
||||||
|
|
||||||
%%
|
cache_put(Key, Val, Time, #cache{}=S) ->
|
||||||
%%
|
case lists:splitwith(fun(X) -> X#heap.expire > Time end, S#cache.heap) of
|
||||||
remove(Key, S) ->
|
{[], _Tail} ->
|
||||||
Cell = evict_key(Key, cache_heap:cells(S#cache.heap)),
|
cache_put(Key, Val, S);
|
||||||
?DEBUG("cache ~p: remove ~p in cell ~p~n", [S#cache.name, Key, Cell]),
|
{Head, Tail} ->
|
||||||
S.
|
[Heap | Rest] = lists:reverse(Head),
|
||||||
|
true = ets:insert(Heap#heap.id, {Key, Val}),
|
||||||
|
lists:foreach(
|
||||||
|
fun(X) -> ets:delete(X#heap.id, Key) end,
|
||||||
|
Rest ++ Tail
|
||||||
|
),
|
||||||
|
cache_util:stats(S#cache.stats, {cache, S#cache.name, put}),
|
||||||
|
?DEBUG("cache ~p: put ~p to heap ~p~n", [S#cache.name, Key, Heap#heap.id]),
|
||||||
|
S
|
||||||
|
end.
|
||||||
|
|
||||||
%%
|
cache_get(Key, #cache{policy=mru}=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
|
% cache always evicts last generation
|
||||||
% MRU caches do not prolong recently used entity
|
% MRU caches do not prolong recently used entity
|
||||||
dowhile(
|
case heap_lookup(Key, S#cache.heap) of
|
||||||
fun(Cell) ->
|
undefined ->
|
||||||
case ets:lookup(Cell, Key) of
|
cache_util:stats(S#cache.stats, {cache, S#cache.name, miss}),
|
||||||
[] ->
|
undefined;
|
||||||
false;
|
{Heap, Val} ->
|
||||||
[{_, Val}] ->
|
?DEBUG("cache ~p: get ~p at cell ~p~n", [S#cache.name, Key, Heap#heap.id]),
|
||||||
?DEBUG("cache ~p: get ~p at cell ~p~n", [S#cache.name, Key, Cell]),
|
cache_util:stats(S#cache.stats, {cache, S#cache.name, hit}),
|
||||||
Val
|
Val
|
||||||
end
|
end;
|
||||||
end,
|
|
||||||
lists:reverse(cache_heap:cells(S#cache.heap))
|
|
||||||
);
|
|
||||||
|
|
||||||
lookup(Key, S) ->
|
cache_get(Key, #cache{}=S) ->
|
||||||
[Head | Tail] = cache_heap:cells(S#cache.heap),
|
Head = hd(S#cache.heap),
|
||||||
case ets:lookup(Head, Key) of
|
case heap_lookup(Key, S#cache.heap) of
|
||||||
% no value at head chunk, lookup and evict tail
|
undefined ->
|
||||||
[] ->
|
cache_util:stats(S#cache.stats, {cache, S#cache.name, miss}),
|
||||||
dowhile(
|
undefined;
|
||||||
fun(Cell) ->
|
{Heap, Val} when Heap#heap.id =:= Head#heap.id ->
|
||||||
case ets:lookup(Cell, Key) of
|
?DEBUG("cache ~p: get ~p at cell ~p~n", [S#cache.name, Key, Heap#heap.id]),
|
||||||
[] ->
|
cache_util:stats(S#cache.stats, {cache, S#cache.name, hit}),
|
||||||
false;
|
Val;
|
||||||
[{_, Val}] ->
|
{Heap, Val} ->
|
||||||
?DEBUG("cache ~p: get ~p at cell ~p~n", [S#cache.name, Key, Cell]),
|
true = ets:insert(Head#heap.id, {Key, Val}),
|
||||||
_ = ets:delete(Cell, Key),
|
_ = ets:delete(Heap#heap.id, Key),
|
||||||
true = ets:insert(Head, {Key, Val}),
|
?DEBUG("cache ~p: get ~p at cell ~p~n", [S#cache.name, Key, Heap#heap.id]),
|
||||||
Val
|
cache_util:stats(S#cache.stats, {cache, S#cache.name, hit}),
|
||||||
end
|
|
||||||
end,
|
|
||||||
Tail
|
|
||||||
);
|
|
||||||
[{_, Val}] ->
|
|
||||||
?DEBUG("cache ~p: get ~p in cell ~p~n", [S#cache.name, Key, Head]),
|
|
||||||
Val
|
Val
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
cache_lookup(Key, #cache{}=S) ->
|
||||||
|
case heap_lookup(Key, S#cache.heap) of
|
||||||
|
undefined ->
|
||||||
|
cache_util:stats(S#cache.stats, {cache, S#cache.name, miss}),
|
||||||
|
undefined;
|
||||||
|
{Heap, Val} ->
|
||||||
|
?DEBUG("cache ~p: get ~p at cell ~p~n", [S#cache.name, Key, Heap#heap.id]),
|
||||||
|
cache_util:stats(S#cache.stats, {cache, S#cache.name, hit}),
|
||||||
|
Val
|
||||||
|
end.
|
||||||
|
|
||||||
|
cache_has(Key, #cache{}=S) ->
|
||||||
|
case heap_has(Key, S#cache.heap) of
|
||||||
|
false ->
|
||||||
|
false;
|
||||||
|
Heap ->
|
||||||
|
?DEBUG("cache ~p: has ~p at cell ~p~n", [S#cache.name, Key, Heap#heap.id]),
|
||||||
|
true
|
||||||
|
end.
|
||||||
|
|
||||||
|
cache_ttl(Key, #cache{}=S) ->
|
||||||
|
case heap_has(Key, S#cache.heap) of
|
||||||
|
false ->
|
||||||
|
undefined;
|
||||||
|
Heap ->
|
||||||
|
Heap#heap.expire - cache_util:now()
|
||||||
|
end.
|
||||||
|
|
||||||
|
cache_remove(Key, #cache{}=S) ->
|
||||||
|
lists:foreach(
|
||||||
|
fun(X) -> ets:delete(X#heap.id, Key) end,
|
||||||
|
S#cache.heap
|
||||||
|
),
|
||||||
|
cache_util:stats(S#cache.stats, {cache, S#cache.name, remove}),
|
||||||
|
?DEBUG("cache ~p: remove ~p~n", [S#cache.name, Key]),
|
||||||
|
S.
|
||||||
|
|
||||||
%%
|
%%
|
||||||
%% execute predicate while it succeeded
|
%%
|
||||||
dowhile(Pred, [Head | Tail]) ->
|
heap_lookup(Key, [H | Tail]) ->
|
||||||
case Pred(Head) of
|
case ets:lookup(H#heap.id, Key) of
|
||||||
% predicate false, apply predicate to next chunk
|
[] -> heap_lookup(Key, Tail);
|
||||||
false -> dowhile(Pred, Tail);
|
[{_, Val}] -> {H, Val}
|
||||||
Result -> Result
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
dowhile(_Pred, []) ->
|
heap_lookup(_Key, []) ->
|
||||||
undefined.
|
undefined.
|
||||||
|
|
||||||
%%
|
%%
|
||||||
%%
|
%%
|
||||||
evict(#cache{}=S) ->
|
heap_has(Key, [H | Tail]) ->
|
||||||
Cell = cache_heap:last(S#cache.heap),
|
case ets:member(H#heap.id, Key) of
|
||||||
?DEBUG("cache ~p: free cell ~p~n", [S#cache.name, Cell]),
|
false -> heap_has(Key, Tail);
|
||||||
|
true -> H
|
||||||
|
end;
|
||||||
|
|
||||||
|
heap_has(_Key, []) ->
|
||||||
|
false.
|
||||||
|
|
||||||
|
|
||||||
|
% %%
|
||||||
|
% %%
|
||||||
|
% 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)
|
||||||
|
% ).
|
||||||
|
|
||||||
|
|
||||||
|
%%
|
||||||
|
%% init cache heap
|
||||||
|
init_heap(#cache{}=S) ->
|
||||||
|
Id = ets:new(undefined, [set, protected]),
|
||||||
|
?DEBUG("cache ~p: init heap ~p~n", [S#cache.name, Id]),
|
||||||
|
Heap = #heap{
|
||||||
|
id = Id,
|
||||||
|
expire = cache_util:madd(S#cache.ttl, cache_util:now()),
|
||||||
|
cardinality = cache_util:mdiv(S#cache.cardinality, S#cache.n),
|
||||||
|
memory = cache_util:mdiv(S#cache.memory, S#cache.n)
|
||||||
|
},
|
||||||
S#cache{
|
S#cache{
|
||||||
heap = cache_heap:alloc(cache_heap:free(Cell, S#cache.heap))
|
heap = [Heap | S#cache.heap]
|
||||||
}.
|
}.
|
||||||
|
|
||||||
%%
|
%%
|
||||||
%%
|
%%
|
||||||
quota(#cache{}=S) ->
|
free_heap(#cache{}=S) ->
|
||||||
maybe_memory_quota(
|
[H | Tail] = lists:reverse(S#cache.heap),
|
||||||
maybe_size_quota(S)
|
Size = ets:info(H#heap.id, size),
|
||||||
|
ets:delete(H#heap.id),
|
||||||
|
cache_util:stats(S#cache.stats, {cache, S#cache.name, evicted}, Size),
|
||||||
|
?DEBUG("cache ~p: free heap ~p~n", [S#cache.name, H#heap.id]),
|
||||||
|
init_heap(
|
||||||
|
S#cache{
|
||||||
|
heap = lists:reverse(Tail)
|
||||||
|
}
|
||||||
).
|
).
|
||||||
|
|
||||||
|
%%
|
||||||
|
%%
|
||||||
|
is_heap_out_of_quota(#heap{}=H) ->
|
||||||
|
is_out_of_memory(H) orelse is_out_of_capacity(H).
|
||||||
|
|
||||||
|
is_out_of_capacity(#heap{cardinality=undefined}) ->
|
||||||
|
false;
|
||||||
|
is_out_of_capacity(#heap{cardinality=N}=H) ->
|
||||||
|
ets:info(H#heap.id, size) >= N.
|
||||||
|
|
||||||
|
is_out_of_memory(#heap{memory=undefined}) ->
|
||||||
|
false;
|
||||||
|
is_out_of_memory(#heap{memory=N}=H) ->
|
||||||
|
ets:info(H#heap.id, memory) >= N.
|
||||||
|
|
||||||
|
|
||||||
maybe_size_quota(#cache{quota_size=undefined}=S) ->
|
|
||||||
S;
|
|
||||||
maybe_size_quota(S) ->
|
|
||||||
Quota = S#cache.quota_size div S#cache.n,
|
|
||||||
[Head | _] = cache_heap:cells(S#cache.heap),
|
|
||||||
case ets:info(Head, size) of
|
|
||||||
Val when Val >= Quota ->
|
|
||||||
evict(S);
|
|
||||||
_ ->
|
|
||||||
S
|
|
||||||
end.
|
|
||||||
|
|
||||||
maybe_memory_quota(#cache{quota_memory=undefined}=S) ->
|
|
||||||
S;
|
|
||||||
maybe_memory_quota(S) ->
|
|
||||||
Quota = (S#cache.quota_memory div erlang:system_info(wordsize)) div S#cache.n,
|
|
||||||
[Head | _] = cache_heap:cells(S#cache.heap),
|
|
||||||
case ets:info(Head, memory) of
|
|
||||||
Val when Val >= Quota ->
|
|
||||||
evict(S);
|
|
||||||
_ ->
|
|
||||||
S
|
|
||||||
end.
|
|
||||||
|
@ -1,86 +0,0 @@
|
|||||||
%%
|
|
||||||
%% 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, 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]
|
|
||||||
}.
|
|
||||||
|
|
||||||
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).
|
|
@ -1,4 +1,23 @@
|
|||||||
%%
|
%%
|
||||||
|
%% 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
|
||||||
|
%% root cache supervisor
|
||||||
-module(cache_sup).
|
-module(cache_sup).
|
||||||
-behaviour(supervisor).
|
-behaviour(supervisor).
|
||||||
|
|
||||||
|
88
src/cache_util.erl
Normal file
88
src/cache_util.erl
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
%%
|
||||||
|
%% 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
|
||||||
|
%%
|
||||||
|
-module(cache_util).
|
||||||
|
|
||||||
|
-export([
|
||||||
|
mdiv/2, madd/2, mmul/2, now/0, stats/2, stats/3, timeout/2
|
||||||
|
]).
|
||||||
|
|
||||||
|
%%
|
||||||
|
%%
|
||||||
|
mdiv(undefined, _) ->
|
||||||
|
undefined;
|
||||||
|
mdiv(X, Y) ->
|
||||||
|
X div Y.
|
||||||
|
|
||||||
|
%%
|
||||||
|
%%
|
||||||
|
madd(undefined, _) ->
|
||||||
|
undefined;
|
||||||
|
madd(X, Y) ->
|
||||||
|
X + Y.
|
||||||
|
|
||||||
|
%%
|
||||||
|
%%
|
||||||
|
mmul(undefined, _) ->
|
||||||
|
undefined;
|
||||||
|
mmul(X, Y) ->
|
||||||
|
X * Y.
|
||||||
|
|
||||||
|
|
||||||
|
%%
|
||||||
|
%%
|
||||||
|
now() ->
|
||||||
|
{Mega, Sec, _} = erlang:now(),
|
||||||
|
Mega * 1000000 + Sec.
|
||||||
|
|
||||||
|
%%
|
||||||
|
%%
|
||||||
|
stats(undefined, _) ->
|
||||||
|
ok;
|
||||||
|
stats({M, F}, Counter) ->
|
||||||
|
M:F(Counter);
|
||||||
|
stats(Fun, Counter)
|
||||||
|
when is_function(Fun) ->
|
||||||
|
Fun(Counter).
|
||||||
|
|
||||||
|
stats(undefined, _, _) ->
|
||||||
|
ok;
|
||||||
|
stats({M, F}, Counter, Val) ->
|
||||||
|
M:F(Counter, Val);
|
||||||
|
stats(Fun, Counter, Val)
|
||||||
|
when is_function(Fun) ->
|
||||||
|
Fun(Counter, Val).
|
||||||
|
|
||||||
|
%%
|
||||||
|
%%
|
||||||
|
%%
|
||||||
|
%% re-set alarm after time
|
||||||
|
timeout(T, Msg)
|
||||||
|
when is_integer(T) ->
|
||||||
|
{clock, T, erlang:send_after(T, self(), Msg)};
|
||||||
|
|
||||||
|
timeout({clock, T, Timer}, Msg) ->
|
||||||
|
erlang:cancel_timer(Timer),
|
||||||
|
{clock, T, erlang:send_after(T, self(), Msg)};
|
||||||
|
|
||||||
|
timeout(X, _) ->
|
||||||
|
X.
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user