You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by va...@apache.org on 2020/04/16 23:00:46 UTC

[couchdb] branch prototype/fdb-layer updated: Allow using cached security and revs_limit properties

This is an automated email from the ASF dual-hosted git repository.

vatamane pushed a commit to branch prototype/fdb-layer
in repository https://gitbox.apache.org/repos/asf/couchdb.git


The following commit(s) were added to refs/heads/prototype/fdb-layer by this push:
     new f71c4c0  Allow using cached security and revs_limit properties
f71c4c0 is described below

commit f71c4c0a2a9b8979508487cef8d7ec9bca222c78
Author: Nick Vatamaniuc <va...@apache.org>
AuthorDate: Tue Apr 14 17:43:04 2020 -0400

    Allow using cached security and revs_limit properties
    
    By default, transactions are used to check metadata, and possibly
    reopen the db, to get a current db handle. However, if a `max_age`
    option is provided and db handle was checked less than `max_age`
    milliseconds ago, use properties from that cached handle instead.
    
    The main use of this feature be in pluggable authorization handlers
    where it might be necessary to inspect the security doc multiple times
    for the same request before a final decision is made.
    
    `revs_limit/1` was updated as well, mainly for consistency since it is
    almost identical to `get_security/1`.
---
 src/fabric/src/fabric2_db.erl             | 36 ++++++++++++++++++++++++-------
 src/fabric/src/fabric2_fdb.erl            |  8 +++++--
 src/fabric/test/fabric2_db_misc_tests.erl | 34 ++++++++++++++++++++++++++++-
 3 files changed, 67 insertions(+), 11 deletions(-)

diff --git a/src/fabric/src/fabric2_db.erl b/src/fabric/src/fabric2_db.erl
index 9b9efda..b942261 100644
--- a/src/fabric/src/fabric2_db.erl
+++ b/src/fabric/src/fabric2_db.erl
@@ -50,7 +50,9 @@
     get_instance_start_time/1,
     get_pid/1,
     get_revs_limit/1,
+    get_revs_limit/2,
     get_security/1,
+    get_security/2,
     get_update_seq/1,
     get_user_ctx/1,
     get_uuid/1,
@@ -500,17 +502,21 @@ get_pid(#{}) ->
 
 
 get_revs_limit(#{} = Db) ->
-    #{revs_limit := RevsLimit} = fabric2_fdb:transactional(Db, fun(TxDb) ->
-        fabric2_fdb:ensure_current(TxDb)
-    end),
-    RevsLimit.
+    get_revs_limit(Db, []).
+
+
+get_revs_limit(#{} = Db, Opts) ->
+    CurrentDb = get_cached_db(Db, Opts),
+    maps:get(revs_limit, CurrentDb).
 
 
 get_security(#{} = Db) ->
-    #{security_doc := SecDoc} = fabric2_fdb:transactional(Db, fun(TxDb) ->
-        fabric2_fdb:ensure_current(TxDb)
-    end),
-    SecDoc.
+    get_security(Db, []).
+
+
+get_security(#{} = Db, Opts) ->
+    CurrentDb = get_cached_db(Db, Opts),
+    maps:get(security_doc, CurrentDb).
 
 
 get_update_seq(#{} = Db) ->
@@ -2037,3 +2043,17 @@ open_json_doc(Db, DocId, OpenOpts, DocOpts) ->
         {ok, #doc{} = Doc} ->
             [{doc, couch_doc:to_json_obj(Doc, DocOpts)}]
     end.
+
+
+get_cached_db(#{} = Db, Opts) when is_list(Opts) ->
+    MaxAge = fabric2_util:get_value(max_age, Opts, 0),
+    Now = erlang:monotonic_time(millisecond),
+    Age = Now - maps:get(check_current_ts, Db),
+    case Age < MaxAge of
+        true ->
+            Db;
+        false ->
+            fabric2_fdb:transactional(Db, fun(TxDb) ->
+                fabric2_fdb:ensure_current(TxDb)
+            end)
+    end.
diff --git a/src/fabric/src/fabric2_fdb.erl b/src/fabric/src/fabric2_fdb.erl
index 53102d6..03f3bad 100644
--- a/src/fabric/src/fabric2_fdb.erl
+++ b/src/fabric/src/fabric2_fdb.erl
@@ -231,6 +231,7 @@ create(#{} = Db0, Options) ->
         revs_limit => 1000,
         security_doc => {[]},
         user_ctx => UserCtx,
+        check_current_ts => erlang:monotonic_time(millisecond),
 
         validate_doc_update_funs => [],
         before_doc_update => undefined,
@@ -272,6 +273,7 @@ open(#{} = Db0, Options) ->
         security_doc => {[]},
 
         user_ctx => UserCtx,
+        check_current_ts => erlang:monotonic_time(millisecond),
 
         % Place holders until we implement these
         % bits.
@@ -1273,8 +1275,10 @@ check_db_version(#{} = Db, CheckDbVersion) ->
         case erlfdb:wait(erlfdb:get(Tx, DbVersionKey)) of
             DbVersion ->
                 put(?PDICT_CHECKED_DB_IS_CURRENT, true),
-                on_commit(Tx, fun() -> fabric2_server:store(Db) end),
-                Db;
+                Now = erlang:monotonic_time(millisecond),
+                Db1 = Db#{check_current_ts := Now},
+                on_commit(Tx, fun() -> fabric2_server:store(Db1) end),
+                Db1;
             _NewDBVersion ->
                 fabric2_server:remove(maps:get(name, Db)),
                 throw({?MODULE, reopen})
diff --git a/src/fabric/test/fabric2_db_misc_tests.erl b/src/fabric/test/fabric2_db_misc_tests.erl
index 1959982..9c95ca5 100644
--- a/src/fabric/test/fabric2_db_misc_tests.erl
+++ b/src/fabric/test/fabric2_db_misc_tests.erl
@@ -38,6 +38,7 @@ misc_test_() ->
                 ?TDEF(accessors),
                 ?TDEF(set_revs_limit),
                 ?TDEF(set_security),
+                ?TDEF(get_security_cached),
                 ?TDEF(is_system_db),
                 ?TDEF(validate_dbname),
                 ?TDEF(validate_doc_ids),
@@ -113,6 +114,30 @@ set_security({DbName, Db, _}) ->
     ?assertEqual(SecObj, fabric2_db:get_security(Db2)).
 
 
+get_security_cached({DbName, Db, _}) ->
+    OldSecObj = fabric2_db:get_security(Db),
+    SecObj = {[
+        {<<"admins">>, {[
+            {<<"names">>, [<<"foo1">>]},
+            {<<"roles">>, []}
+        ]}}
+    ]},
+
+    % Set directly so we don't auto-update the local cache
+    {ok, Db1} = fabric2_db:open(DbName, [?ADMIN_CTX]),
+    ?assertMatch({ok, #{}}, fabric2_fdb:transactional(Db1, fun(TxDb) ->
+        fabric2_fdb:set_config(TxDb, security_doc, SecObj)
+    end)),
+
+    {ok, Db2} = fabric2_db:open(DbName, [?ADMIN_CTX]),
+    ?assertEqual(OldSecObj, fabric2_db:get_security(Db2, [{max_age, 1000}])),
+
+    timer:sleep(100),
+    ?assertEqual(SecObj, fabric2_db:get_security(Db2, [{max_age, 50}])),
+
+    ?assertEqual(ok, fabric2_db:set_security(Db2, OldSecObj)).
+
+
 is_system_db({DbName, Db, _}) ->
     ?assertEqual(false, fabric2_db:is_system_db(Db)),
     ?assertEqual(false, fabric2_db:is_system_db_name("foo")),
@@ -305,12 +330,19 @@ metadata_bump({DbName, _, _}) ->
         erlfdb:wait(erlfdb:get(Tx, ?METADATA_VERSION_KEY))
     end),
 
+    % Save timetamp before ensure_current/1 is called
+    TsBeforeEnsureCurrent = erlang:monotonic_time(millisecond),
+
     % Perform a random operation which calls ensure_current
     {ok, _} = fabric2_db:get_db_info(Db),
 
     % Check that db handle in the cache got the new metadata version
+    % and that check_current_ts was updated
     CachedDb = fabric2_server:fetch(DbName, undefined),
-    ?assertMatch(#{md_version := NewMDVersion}, CachedDb).
+    ?assertMatch(#{
+        md_version := NewMDVersion,
+        check_current_ts := Ts
+    } when Ts >= TsBeforeEnsureCurrent, CachedDb).
 
 
 db_version_bump({DbName, _, _}) ->