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).