You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by ja...@apache.org on 2019/11/19 00:27:18 UTC
[couchdb] 06/06: Cleanup & test
This is an automated email from the ASF dual-hosted git repository.
jaydoane pushed a commit to branch expiring-cache
in repository https://gitbox.apache.org/repos/asf/couchdb.git
commit dfefffbb499d4892127ad6ee925866948587b94c
Author: Jay Doane <ja...@apache.org>
AuthorDate: Mon Nov 18 16:22:34 2019 -0800
Cleanup & test
---
rebar.config.script | 1 +
rel/reltool.config | 2 +
.../include/couch_expiring_cache.hrl | 1 +
.../src/couch_expiring_cache.erl | 8 ++
.../src/couch_expiring_cache_fdb.erl | 140 ++++++---------------
.../src/couch_expiring_cache_server.erl | 45 +++++--
.../test/couch_expiring_cache_tests.erl | 86 +++++++++++++
7 files changed, 167 insertions(+), 116 deletions(-)
diff --git a/rebar.config.script b/rebar.config.script
index 8ef1abc..d867de5 100644
--- a/rebar.config.script
+++ b/rebar.config.script
@@ -76,6 +76,7 @@ SubDirs = [
"src/couch",
"src/couch_eval",
"src/couch_event",
+ "src/couch_expiring_cache",
"src/mem3",
"src/couch_index",
"src/couch_mrview",
diff --git a/rel/reltool.config b/rel/reltool.config
index caeea38..72272b5 100644
--- a/rel/reltool.config
+++ b/rel/reltool.config
@@ -43,6 +43,7 @@
couch_eval,
couch_js,
couch_event,
+ couch_expiring_cache,
couch_peruser,
couch_views,
ddoc_cache,
@@ -105,6 +106,7 @@
{app, couch_replicator, [{incl_cond, include}]},
{app, couch_stats, [{incl_cond, include}]},
{app, couch_event, [{incl_cond, include}]},
+ {app, couch_expiring_cache, [{incl_cond, include}]},
{app, couch_peruser, [{incl_cond, include}]},
{app, couch_views, [{incl_cond, include}]},
{app, ddoc_cache, [{incl_cond, include}]},
diff --git a/src/couch_expiring_cache/include/couch_expiring_cache.hrl b/src/couch_expiring_cache/include/couch_expiring_cache.hrl
new file mode 100644
index 0000000..fe771c5
--- /dev/null
+++ b/src/couch_expiring_cache/include/couch_expiring_cache.hrl
@@ -0,0 +1 @@
+-define(TIME_UNIT, millisecond).
diff --git a/src/couch_expiring_cache/src/couch_expiring_cache.erl b/src/couch_expiring_cache/src/couch_expiring_cache.erl
index 62b6367..61f4c20 100644
--- a/src/couch_expiring_cache/src/couch_expiring_cache.erl
+++ b/src/couch_expiring_cache/src/couch_expiring_cache.erl
@@ -6,8 +6,16 @@
]).
+-type millisecond() :: non_neg_integer().
+
+
+-spec insert(Name :: binary(), Key :: binary(), Value :: binary(),
+ StaleTS :: millisecond(), ExpiresTS :: millisecond()) -> ok.
insert(Name, Key, Value, StaleTS, ExpiresTS) ->
couch_expiring_cache_fdb:insert(Name, Key, Value, StaleTS, ExpiresTS).
+
+-spec lookup(Name :: binary(), Key :: binary()) ->
+ not_found | {fresh, Val :: binary()} | {stale, Val :: binary()} | expired.
lookup(Name, Key) ->
couch_expiring_cache_fdb:lookup(Name, Key).
diff --git a/src/couch_expiring_cache/src/couch_expiring_cache_fdb.erl b/src/couch_expiring_cache/src/couch_expiring_cache_fdb.erl
index 283a92b..febf103 100644
--- a/src/couch_expiring_cache/src/couch_expiring_cache_fdb.erl
+++ b/src/couch_expiring_cache/src/couch_expiring_cache_fdb.erl
@@ -13,27 +13,20 @@
-module(couch_expiring_cache_fdb).
-export([
- clear_expired_range/2,
- get_range/2,
- cache_prefix/1,
- layer_prefix/1,
- list_keys/1,
- list_exp/0,
- remove_exp/3,
- %% delete_all/1,
insert/5,
lookup/2,
- remove_primary/2
+ clear_expired_range/2
]).
--define(DEFAULT_LIMIT, 100000). % How to enforce?
-
-define(XC, 53). % coordinate with fabric2.hrl
-define(PK, 1).
-define(EXP, 2).
+-include_lib("couch_expiring_cache/include/couch_expiring_cache.hrl").
+
+
% Data model
% see: https://forums.foundationdb.org/t/designing-key-value-expiration-in-fdb/156
%
@@ -41,63 +34,45 @@
% (?XC, ?EXP, ExpireTS, Name, Key) := ()
-list_keys(Name) ->
- Callback = fun(Key, Acc) -> [Key | Acc] end,
- fabric2_fdb:transactional(fun(Tx) ->
- list_keys_int(Name, Callback, Tx)
- end).
-
-list_keys_int(Name, Callback, Tx) ->
- Prefix = erlfdb_tuple:pack({?XC, ?PK, Name}, layer_prefix(Tx)),
- fabric2_fdb:fold_range({tx, Tx}, Prefix, fun({K, _V}, Acc) ->
- {Key} = erlfdb_tuple:unpack(K, Prefix),
- Callback(Key, Acc)
- end, [], []).
-
-
-list_exp() ->
- Callback = fun(Key, Acc) -> [Key | Acc] end,
- fabric2_fdb:transactional(fun(Tx) ->
- Prefix = erlfdb_tuple:pack({?XC, ?EXP}, layer_prefix(Tx)),
- fabric2_fdb:fold_range({tx, Tx}, Prefix, fun({K, _V}, Acc) ->
- Unpacked = {_ExpiresTS, _Name, _Key} = erlfdb_tuple:unpack(K, Prefix),
- Callback(Unpacked, Acc)
- end, [], [])
- end).
-
-
-get_range(EndTS, Limit) when Limit > 0 ->
- Callback = fun(Key, Acc) -> [Key | Acc] end,
+insert(Name, Key, Val, StaleTS, ExpiresTS) ->
+ LayerPrefix = couch_expiring_cache_server:layer_prefix(),
+ PK = primary_key(Name, Key, LayerPrefix),
+ PV = erlfdb_tuple:pack({Val, StaleTS, ExpiresTS}),
+ XK = expiry_key(ExpiresTS, Name, Key, LayerPrefix),
+ XV = erlfdb_tuple:pack({}),
fabric2_fdb:transactional(fun(Tx) ->
- Prefix = erlfdb_tuple:pack({?XC, ?EXP}, layer_prefix(Tx)),
- fabric2_fdb:fold_range({tx, Tx}, Prefix, fun({K, _V}, Acc) ->
- Unpacked = {_ExpiresTS, _Name, _Key} = erlfdb_tuple:unpack(K, Prefix),
- Callback(Unpacked, Acc)
- end, [], [{end_key, EndTS}, {limit, Limit}])
+ ok = erlfdb:set(Tx, PK, PV),
+ ok = erlfdb:set(Tx, XK, XV)
end).
-remove_exp(ExpiresTS, Name, Key) ->
+lookup(Name, Key) ->
+ LayerPrefix = couch_expiring_cache_server:layer_prefix(),
+ PK = primary_key(Name, Key, LayerPrefix),
fabric2_fdb:transactional(fun(Tx) ->
- clear_expired(Tx, ExpiresTS, Name, Key, layer_prefix(Tx))
+ case erlfdb:wait(erlfdb:get(Tx, PK)) of
+ not_found ->
+ not_found;
+ Bin when is_binary(Bin) ->
+ {Val, StaleTS, ExpiresTS} = erlfdb_tuple:unpack(Bin),
+ Now = erlang:system_time(?TIME_UNIT),
+ if
+ Now < StaleTS -> {fresh, Val};
+ Now < ExpiresTS -> {stale, Val};
+ true -> expired
+ end
+ end
end).
-clear_expired(Tx, ExpiresTS, Name, Key, Prefix) ->
- PK = primary_key(Name, Key, Prefix),
- XK = expiry_key(ExpiresTS, Name, Key, Prefix),
- ok = erlfdb:clear(Tx, PK),
- ok = erlfdb:clear(Tx, XK).
-
-
clear_expired_range(EndTS, Limit) when Limit > 0 ->
Callback = fun
(TS, 0) when TS > 0 -> TS;
(TS, Acc) -> min(TS, Acc)
end,
+ LayerPrefix = couch_expiring_cache_server:layer_prefix(),
+ ExpiresPrefix = erlfdb_tuple:pack({?XC, ?EXP}, LayerPrefix),
fabric2_fdb:transactional(fun(Tx) ->
- LayerPrefix = layer_prefix(Tx),
- ExpiresPrefix = erlfdb_tuple:pack({?XC, ?EXP}, LayerPrefix),
fabric2_fdb:fold_range({tx, Tx}, ExpiresPrefix, fun({K, _V}, Acc) ->
Unpacked = erlfdb_tuple:unpack(K, ExpiresPrefix),
couch_log:info("~p clearing ~p", [?MODULE, Unpacked]),
@@ -108,45 +83,14 @@ clear_expired_range(EndTS, Limit) when Limit > 0 ->
end).
-insert(Name, Key, Val, StaleTS, ExpiresTS) ->
- fabric2_fdb:transactional(fun(Tx) ->
- Prefix = layer_prefix(Tx),
-
- PK = primary_key(Name, Key, Prefix),
- PV = erlfdb_tuple:pack({Val, StaleTS, ExpiresTS}),
- ok = erlfdb:set(Tx, PK, PV),
-
- XK = expiry_key(ExpiresTS, Name, Key, Prefix),
- XV = erlfdb_tuple:pack({}),
- ok = erlfdb:set(Tx, XK, XV)
- end).
-
-
-lookup(Name, Key) ->
- fabric2_fdb:transactional(fun(Tx) ->
- Prefix = layer_prefix(Tx),
-
- PK = primary_key(Name, Key, Prefix),
- case erlfdb:wait(erlfdb:get(Tx, PK)) of
- not_found ->
- not_found;
- Bin when is_binary(Bin) ->
- {Val, StaleTS, ExpiresTS} = erlfdb_tuple:unpack(Bin),
- Now = erlang:system_time(second),
- if
- Now < StaleTS -> {fresh, Val};
- Now < ExpiresTS -> {stale, Val};
- true -> expired
- end
- end
- end).
+%% Private
-remove_primary(Name, Key) ->
- fabric2_fdb:transactional(fun(Tx) ->
- PK = primary_key(Name, Key, layer_prefix(Tx)),
- erlfdb:clear(Tx, PK)
- end).
+clear_expired(Tx, ExpiresTS, Name, Key, Prefix) ->
+ PK = primary_key(Name, Key, Prefix),
+ XK = expiry_key(ExpiresTS, Name, Key, Prefix),
+ ok = erlfdb:clear(Tx, PK),
+ ok = erlfdb:clear(Tx, XK).
primary_key(Name, Key, Prefix) ->
@@ -155,17 +99,3 @@ primary_key(Name, Key, Prefix) ->
expiry_key(ExpiresTS, Name, Key, Prefix) ->
erlfdb_tuple:pack({?XC, ?EXP, ExpiresTS, Name, Key}, Prefix).
-
-
-layer_prefix(Tx) ->
- fabric2_fdb:get_dir(Tx).
-
-
-cache_prefix(Tx) ->
- erlfdb_tuple:pack({?XC}, layer_prefix(Tx)).
-
-
-
--ifdef(TEST).
--include_lib("eunit/include/eunit.hrl").
--endif.
diff --git a/src/couch_expiring_cache/src/couch_expiring_cache_server.erl b/src/couch_expiring_cache/src/couch_expiring_cache_server.erl
index 82a90aa..e14e863 100644
--- a/src/couch_expiring_cache/src/couch_expiring_cache_server.erl
+++ b/src/couch_expiring_cache/src/couch_expiring_cache_server.erl
@@ -27,23 +27,31 @@
code_change/3
]).
+-export([
+ layer_prefix/0
+]).
+
--define(DEFAULT_BATCH, 1000).
+-define(DEFAULT_BATCH_SIZE, 1000).
-define(DEFAULT_PERIOD_MS, 5000).
-define(DEFAULT_MAX_JITTER_MS, 1000).
+-include_lib("couch_expiring_cache/include/couch_expiring_cache.hrl").
+
+
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
init(_) ->
- Ref = schedule_remove_expired(),
+ ?MODULE = ets:new(?MODULE, [named_table, public, {read_concurrency, true}]),
+ true = set_layer_prefix(),
{ok, #{
- timer_ref => Ref,
- lag => 0,
+ timer_ref => schedule_remove_expired(),
last_removal => 0,
- min_ts => 0}}.
+ oldest_ts => 0,
+ lag => 0}}.
terminate(_, _) ->
@@ -58,15 +66,15 @@ handle_cast(Msg, St) ->
{stop, {bad_cast, Msg}, St}.
-handle_info(remove_expired, St = #{min_ts := MinTS0}) ->
- Now = erlang:system_time(second),
- MinTS = max(MinTS0, remove_expired(Now)),
+handle_info(remove_expired, St = #{oldest_ts := OldestTS0}) ->
+ Now = erlang:system_time(?TIME_UNIT),
+ OldestTS = max(OldestTS0, remove_expired(Now)),
Ref = schedule_remove_expired(),
{noreply, St#{
timer_ref := Ref,
- lag := Now - MinTS,
last_removal := Now,
- min_ts := MinTS}};
+ oldest_ts := OldestTS,
+ lag := Now - OldestTS}};
handle_info(Msg, St) ->
{stop, {bad_info, Msg}, St}.
@@ -76,6 +84,21 @@ code_change(_OldVsn, St, _Extra) ->
{ok, St}.
+layer_prefix() ->
+ [{layer_prefix, Prefix}] = ets:lookup(?MODULE, layer_prefix),
+ Prefix.
+
+
+%% Private
+
+
+set_layer_prefix() ->
+ fabric2_fdb:transactional(fun(Tx) ->
+ LayerPrefix = fabric2_fdb:get_dir(Tx),
+ true = ets:insert(?MODULE, {layer_prefix, LayerPrefix})
+ end).
+
+
remove_expired(EndTS) ->
couch_expiring_cache_fdb:clear_expired_range(EndTS, batch_size()).
@@ -99,4 +122,4 @@ max_jitter_ms() ->
batch_size() ->
config:get_integer("couch_expiring_cache", "batch_size",
- ?DEFAULT_BATCH).
+ ?DEFAULT_BATCH_SIZE).
diff --git a/src/couch_expiring_cache/test/couch_expiring_cache_tests.erl b/src/couch_expiring_cache/test/couch_expiring_cache_tests.erl
new file mode 100644
index 0000000..95748e2
--- /dev/null
+++ b/src/couch_expiring_cache/test/couch_expiring_cache_tests.erl
@@ -0,0 +1,86 @@
+% Licensed under the Apache License, Version 2.0 (the "License"); you may not
+% use this file except in compliance with the License. You may obtain a copy of
+% the License at
+%
+% http://www.apache.org/licenses/LICENSE-2.0
+%
+% Unless required by applicable law or agreed to in writing, software
+% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+% License for the specific language governing permissions and limitations under
+% the License.
+
+-module(couch_expiring_cache_tests).
+
+
+-include_lib("couch/include/couch_db.hrl").
+-include_lib("couch/include/couch_eunit.hrl").
+-include_lib("eunit/include/eunit.hrl").
+
+-include_lib("couch_expiring_cache/include/couch_expiring_cache.hrl").
+
+
+couch_expiring_cache_basic_test_() ->
+ {
+ "Test expiring cache basics",
+ {
+ setup,
+ fun setup_couch/0, fun teardown_couch/1,
+ {
+ foreach,
+ fun setup/0, fun teardown/1,
+ [
+ fun simple_lifecycle/1
+ ]
+ }
+ }
+ }.
+
+
+setup_couch() ->
+ test_util:start_couch([fabric]).
+
+
+teardown_couch(Ctx) ->
+ test_util:stop_couch(Ctx),
+ meck:unload().
+
+
+setup() ->
+ config:set("couch_expiring_cache", "period_ms", "20", false),
+ config:set("couch_expiring_cache", "max_jitter_ms", "0", false),
+ application:start(couch_expiring_cache),
+ #{}.
+
+
+teardown(#{}) ->
+ application:stop(couch_expiring_cache),
+ meck:unload().
+
+
+simple_lifecycle(#{}) ->
+ ?_test(begin
+ Now = erlang:system_time(?TIME_UNIT),
+ StaleTS = Now + 100,
+ ExpiresTS = Now + 200,
+ Name = <<"test">>,
+ Key = <<"key">>,
+ Val = <<"val">>,
+
+ ?assertEqual(not_found, couch_expiring_cache:lookup(Name, Key)),
+ ?assertEqual(ok,
+ couch_expiring_cache:insert(Name, Key, Val, StaleTS, ExpiresTS)),
+ ?assertEqual({fresh, Val}, couch_expiring_cache:lookup(Name, Key)),
+ wait_lookup(Name, Key, {stale, Val}),
+ wait_lookup(Name, Key, expired),
+ wait_lookup(Name, Key, not_found)
+ end).
+
+
+wait_lookup(Name, Key, Expect) ->
+ test_util:wait(fun() ->
+ case couch_expiring_cache:lookup(Name, Key) of
+ Expect -> ok;
+ _ -> wait
+ end
+ end, _Timeout = 1000, _PollingInterval = 10).