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 2019/11/14 23:10:05 UTC

[couchdb] branch prototype/fdb-layer updated (3db0ba7 -> 44f660f)

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

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


    from 3db0ba7  Ensure we can create partitioned design docs with FDB
     new aaae564  Check security properties in the main transaction
     new b71cbe2  Before starting a db transanction, refresh the db handle from the cache
     new 44f660f  Update fabric2_fdb's set_config to take un-encoding values

The 3 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 src/chttpd/src/chttpd_auth_request.erl        |   5 +-
 src/fabric/src/fabric2_db.erl                 |  67 +++++++++------
 src/fabric/src/fabric2_fdb.erl                | 114 +++++++++++++++-----------
 src/fabric/src/fabric2_server.erl             |   3 +-
 src/fabric/test/fabric2_db_security_tests.erl |  21 +++--
 5 files changed, 123 insertions(+), 87 deletions(-)


[couchdb] 01/03: Check security properties in the main transaction

Posted by va...@apache.org.
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

commit aaae5649a5a2cf38e97e23ac43b9bc6cc11a4fd3
Author: Nick Vatamaniuc <va...@apache.org>
AuthorDate: Tue Nov 5 10:52:34 2019 -0500

    Check security properties in the main transaction
    
    Previously we checked security properties in a separate transaction, after
    opening the db or fetching it from the cache. To avoid running an extra
    transaction move the check inside the main transaction right after the metadata
    check runs. That ensure it will be consistent and it won't be accidentally
    missed as all operations run the `ensure_current` metadata check.
    
    Also remove the special `get_config/2` function in `fabric2_fdb` for getting
    revs limit and security properties and just read them directly from the db map.
---
 src/chttpd/src/chttpd_auth_request.erl        |  5 +--
 src/fabric/src/fabric2_db.erl                 | 59 +++++++++++++++++----------
 src/fabric/src/fabric2_fdb.erl                | 39 ++++++++----------
 src/fabric/src/fabric2_server.erl             |  3 +-
 src/fabric/test/fabric2_db_security_tests.erl | 21 ++++++----
 5 files changed, 71 insertions(+), 56 deletions(-)

diff --git a/src/chttpd/src/chttpd_auth_request.erl b/src/chttpd/src/chttpd_auth_request.erl
index 7210905..26fee17 100644
--- a/src/chttpd/src/chttpd_auth_request.erl
+++ b/src/chttpd/src/chttpd_auth_request.erl
@@ -102,9 +102,8 @@ server_authorization_check(#httpd{method=Method, path_parts=[<<"_utils">>|_]}=Re
 server_authorization_check(#httpd{path_parts=[<<"_", _/binary>>|_]}=Req) ->
     require_admin(Req).
 
-db_authorization_check(#httpd{path_parts=[DbName|_],user_ctx=Ctx}=Req) ->
-    {ok, Db} = fabric2_db:open(DbName, [{user_ctx, Ctx}]),
-    fabric2_db:check_is_member(Db),
+db_authorization_check(#httpd{path_parts=[_DbName|_]}=Req) ->
+    % Db authorization checks are performed in fabric before every FDB operation
     Req.
 
 require_admin(Req) ->
diff --git a/src/fabric/src/fabric2_db.erl b/src/fabric/src/fabric2_db.erl
index e2674a4..b5d68c0 100644
--- a/src/fabric/src/fabric2_db.erl
+++ b/src/fabric/src/fabric2_db.erl
@@ -176,19 +176,18 @@ open(DbName, Options) ->
     case fabric2_server:fetch(DbName) of
         #{} = Db ->
             Db1 = maybe_set_user_ctx(Db, Options),
-            ok = check_is_member(Db1),
-            {ok, Db1};
+            {ok, require_member_check(Db1)};
         undefined ->
             Result = fabric2_fdb:transactional(DbName, Options, fun(TxDb) ->
                 fabric2_fdb:open(TxDb, Options)
             end),
             % Cache outside the transaction retry loop
             case Result of
-                #{security_doc := SecDoc} = Db0 ->
-                    ok = check_is_member(Db0, SecDoc),
+                #{} = Db0 ->
                     Db1 = maybe_add_sys_db_callbacks(Db0),
                     ok = fabric2_server:store(Db1),
-                    {ok, Db1#{tx := undefined}};
+                    Db2 = Db1#{tx := undefined},
+                    {ok, require_member_check(Db2)};
                 Error ->
                     Error
             end
@@ -256,7 +255,11 @@ is_admin(Db, {SecProps}) when is_list(SecProps) ->
 
 
 check_is_admin(Db) ->
-    case is_admin(Db) of
+    check_is_admin(Db, get_security(Db)).
+
+
+check_is_admin(Db, SecDoc) ->
+    case is_admin(Db, SecDoc) of
         true ->
             ok;
         false ->
@@ -280,6 +283,18 @@ check_is_member(Db, SecDoc) ->
     end.
 
 
+require_admin_check(#{} = Db) ->
+    Db#{security_fun := fun check_is_admin/2}.
+
+
+require_member_check(#{} = Db) ->
+    Db#{security_fun := fun check_is_member/2}.
+
+
+no_security_check(#{} = Db) ->
+    Db#{security_fun := undefined}.
+
+
 name(#{name := DbName}) ->
     DbName.
 
@@ -359,17 +374,17 @@ get_pid(#{}) ->
 
 
 get_revs_limit(#{} = Db) ->
-    RevsLimitBin = fabric2_fdb:transactional(Db, fun(TxDb) ->
-        fabric2_fdb:get_config(TxDb, <<"revs_limit">>)
+    #{revs_limit := RevsLimit} = fabric2_fdb:transactional(Db, fun(TxDb) ->
+        fabric2_fdb:ensure_current(TxDb)
     end),
-    ?bin2uint(RevsLimitBin).
+    RevsLimit.
 
 
 get_security(#{} = Db) ->
-    SecBin = fabric2_fdb:transactional(Db, fun(TxDb) ->
-        fabric2_fdb:get_config(TxDb, <<"security_doc">>)
+    #{security_doc := SecDoc} = fabric2_fdb:transactional(Db, fun(TxDb) ->
+        fabric2_fdb:ensure_current(no_security_check(TxDb))
     end),
-    ?JSON_DECODE(SecBin).
+    SecDoc.
 
 
 get_update_seq(#{} = Db) ->
@@ -440,26 +455,26 @@ is_users_db(DbName) when is_binary(DbName) ->
     IsAuthCache orelse IsCfgUsersDb orelse IsGlobalUsersDb.
 
 
-set_revs_limit(#{} = Db, RevsLimit) ->
-    check_is_admin(Db),
+set_revs_limit(#{} = Db0, RevsLimit) ->
+    Db1 = require_admin_check(Db0),
     RevsLimBin = ?uint2bin(max(1, RevsLimit)),
-    Resp = fabric2_fdb:transactional(Db, fun(TxDb) ->
-        fabric2_fdb:set_config(TxDb, <<"revs_limit">>, RevsLimBin)
+    {Resp, Db2} = fabric2_fdb:transactional(Db1, fun(TxDb) ->
+        {fabric2_fdb:set_config(TxDb, <<"revs_limit">>, RevsLimBin), TxDb}
     end),
     if Resp /= ok -> Resp; true ->
-        fabric2_server:store(Db#{revs_limit := RevsLimit})
+        fabric2_server:store(Db2#{revs_limit := RevsLimit})
     end.
 
 
-set_security(#{} = Db, Security) ->
-    check_is_admin(Db),
+set_security(#{} = Db0, Security) ->
+    Db1 = require_admin_check(Db0),
     ok = fabric2_util:validate_security_object(Security),
     SecBin = ?JSON_ENCODE(Security),
-    Resp = fabric2_fdb:transactional(Db, fun(TxDb) ->
-        fabric2_fdb:set_config(TxDb, <<"security_doc">>, SecBin)
+    {Resp, Db2} = fabric2_fdb:transactional(Db1, fun(TxDb) ->
+        {fabric2_fdb:set_config(TxDb, <<"security_doc">>, SecBin), TxDb}
     end),
     if Resp /= ok -> Resp; true ->
-        fabric2_server:store(Db#{security_doc := Security})
+        fabric2_server:store(Db2#{security_doc := Security})
     end.
 
 
diff --git a/src/fabric/src/fabric2_fdb.erl b/src/fabric/src/fabric2_fdb.erl
index c59346e..1392ccd 100644
--- a/src/fabric/src/fabric2_fdb.erl
+++ b/src/fabric/src/fabric2_fdb.erl
@@ -20,7 +20,7 @@
 
     create/2,
     open/2,
-    reopen/1,
+    ensure_current/1,
     delete/1,
     exists/1,
 
@@ -30,7 +30,6 @@
 
     get_info/1,
     get_config/1,
-    get_config/2,
     set_config/3,
 
     get_stat/2,
@@ -246,10 +245,12 @@ reopen(#{} = OldDb) ->
         tx := Tx,
         name := DbName,
         db_options := Options,
-        user_ctx := UserCtx
+        user_ctx := UserCtx,
+        security_fun := SecurityFun
     } = OldDb,
     Options1 = lists:keystore(user_ctx, 1, Options, {user_ctx, UserCtx}),
-    open(init_db(Tx, DbName, Options1), Options1).
+    NewDb = open(init_db(Tx, DbName, Options1), Options1),
+    NewDb#{security_fun := SecurityFun}.
 
 
 delete(#{} = Db) ->
@@ -360,19 +361,6 @@ get_config(#{} = Db) ->
     end, erlfdb:wait(Future)).
 
 
-get_config(#{} = Db, ConfigKey) ->
-    #{
-        tx := Tx,
-        db_prefix := DbPrefix
-    } = ensure_current(Db),
-
-    Key = erlfdb_tuple:pack({?DB_CONFIG, ConfigKey}, DbPrefix),
-    case erlfdb:wait(erlfdb:get(Tx, Key)) of
-        % config values are expected to be set so we blow if not_found
-        Val when Val =/= not_found -> Val
-    end.
-
-
 set_config(#{} = Db, ConfigKey, ConfigVal) ->
     #{
         tx := Tx,
@@ -835,6 +823,7 @@ init_db(Tx, DbName, Options) ->
         layer_prefix => Prefix,
         md_version => Version,
 
+        security_fun => undefined,
         db_options => Options
     }.
 
@@ -1276,12 +1265,20 @@ ensure_current(Db) ->
     ensure_current(Db, true).
 
 
-ensure_current(#{} = Db, CheckDbVersion) ->
-    require_transaction(Db),
-    case check_metadata_version(Db) of
+ensure_current(#{} = Db0, CheckDbVersion) ->
+    require_transaction(Db0),
+    Db2 = case check_metadata_version(Db0) of
         {current, Db1} -> Db1;
         {stale, Db1} -> check_db_version(Db1, CheckDbVersion)
-    end.
+    end,
+    case maps:get(security_fun, Db2) of
+        SecurityFun when is_function(SecurityFun, 2) ->
+            #{security_doc := SecDoc} = Db2,
+            ok = SecurityFun(Db2, SecDoc);
+        undefined ->
+            ok
+    end,
+    Db2.
 
 
 is_transaction_applied(Tx) ->
diff --git a/src/fabric/src/fabric2_server.erl b/src/fabric/src/fabric2_server.erl
index f88ceb6..9dd0b77 100644
--- a/src/fabric/src/fabric2_server.erl
+++ b/src/fabric/src/fabric2_server.erl
@@ -56,7 +56,8 @@ fetch(DbName) when is_binary(DbName) ->
 store(#{name := DbName} = Db0) when is_binary(DbName) ->
     Db1 = Db0#{
         tx := undefined,
-        user_ctx := #user_ctx{}
+        user_ctx := #user_ctx{},
+        security_fun := undefined
     },
     true = ets:insert(?MODULE, {DbName, Db1}),
     ok.
diff --git a/src/fabric/test/fabric2_db_security_tests.erl b/src/fabric/test/fabric2_db_security_tests.erl
index 4a54083..e5f3ad2 100644
--- a/src/fabric/test/fabric2_db_security_tests.erl
+++ b/src/fabric/test/fabric2_db_security_tests.erl
@@ -39,9 +39,9 @@ security_test_() ->
                 fun check_admin_is_member/1,
                 fun check_is_member_of_public_db/1,
                 fun check_set_user_ctx/1,
-                fun check_open_forbidden/1,
-                fun check_fail_open_no_opts/1,
-                fun check_fail_open_name_null/1
+                fun check_forbidden/1,
+                fun check_fail_no_opts/1,
+                fun check_fail_name_null/1
             ]}
         }
     }.
@@ -165,18 +165,21 @@ check_set_user_ctx({Db0, _, _}) ->
     ?assertEqual(UserCtx, fabric2_db:get_user_ctx(Db1)).
 
 
-check_open_forbidden({Db0, _, _}) ->
+check_forbidden({Db0, _, _}) ->
     DbName = fabric2_db:name(Db0),
     UserCtx = #user_ctx{name = <<"foo">>, roles = [<<"bar">>]},
-    ?assertThrow({forbidden, _}, fabric2_db:open(DbName, [{user_ctx, UserCtx}])).
+    {ok, Db} = fabric2_db:open(DbName, [{user_ctx, UserCtx}]),
+    ?assertThrow({forbidden, _}, fabric2_db:get_db_info(Db)).
 
 
-check_fail_open_no_opts({Db0, _, _}) ->
+check_fail_no_opts({Db0, _, _}) ->
     DbName = fabric2_db:name(Db0),
-    ?assertThrow({unauthorized, _}, fabric2_db:open(DbName, [])).
+    {ok, Db} = fabric2_db:open(DbName, []),
+    ?assertThrow({unauthorized, _}, fabric2_db:get_db_info(Db)).
 
 
-check_fail_open_name_null({Db0, _, _}) ->
+check_fail_name_null({Db0, _, _}) ->
     DbName = fabric2_db:name(Db0),
     UserCtx = #user_ctx{name = null},
-    ?assertThrow({unauthorized, _}, fabric2_db:open(DbName, [{user_ctx, UserCtx}])).
+    {ok, Db} = fabric2_db:open(DbName, [{user_ctx, UserCtx}]),
+    ?assertThrow({unauthorized, _}, fabric2_db:get_db_info(Db)).


[couchdb] 02/03: Before starting a db transanction, refresh the db handle from the cache

Posted by va...@apache.org.
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

commit b71cbe214e3202aee4d4923b5dbd17414e3dc91e
Author: Nick Vatamaniuc <va...@apache.org>
AuthorDate: Fri Nov 8 13:27:16 2019 -0500

    Before starting a db transanction, refresh the db handle from the cache
    
    Previously, a stale db handle could be re-used across a few separate
    transactions. That would result in the database getting re-opened before every
    one of those operations.
    
    To prevent that from happening, check the cache before the transaction starts,
    and if there is a newer version of the db handle and use that.
---
 src/fabric/src/fabric2_db.erl  | 22 ++++++++++++-------
 src/fabric/src/fabric2_fdb.erl | 49 +++++++++++++++++++++++++++++++-----------
 2 files changed, 50 insertions(+), 21 deletions(-)

diff --git a/src/fabric/src/fabric2_db.erl b/src/fabric/src/fabric2_db.erl
index b5d68c0..ff5371f 100644
--- a/src/fabric/src/fabric2_db.erl
+++ b/src/fabric/src/fabric2_db.erl
@@ -458,11 +458,14 @@ is_users_db(DbName) when is_binary(DbName) ->
 set_revs_limit(#{} = Db0, RevsLimit) ->
     Db1 = require_admin_check(Db0),
     RevsLimBin = ?uint2bin(max(1, RevsLimit)),
-    {Resp, Db2} = fabric2_fdb:transactional(Db1, fun(TxDb) ->
-        {fabric2_fdb:set_config(TxDb, <<"revs_limit">>, RevsLimBin), TxDb}
+    Resp = fabric2_fdb:transactional(Db1, fun(TxDb) ->
+        fabric2_fdb:set_config(TxDb, <<"revs_limit">>, RevsLimBin)
     end),
-    if Resp /= ok -> Resp; true ->
-        fabric2_server:store(Db2#{revs_limit := RevsLimit})
+    case Resp of
+        {ok, #{} = Db2} ->
+             fabric2_server:store(Db2#{revs_limit := RevsLimit});
+        Err ->
+            Err
     end.
 
 
@@ -470,11 +473,14 @@ set_security(#{} = Db0, Security) ->
     Db1 = require_admin_check(Db0),
     ok = fabric2_util:validate_security_object(Security),
     SecBin = ?JSON_ENCODE(Security),
-    {Resp, Db2} = fabric2_fdb:transactional(Db1, fun(TxDb) ->
-        {fabric2_fdb:set_config(TxDb, <<"security_doc">>, SecBin), TxDb}
+    Resp = fabric2_fdb:transactional(Db1, fun(TxDb) ->
+        fabric2_fdb:set_config(TxDb, <<"security_doc">>, SecBin)
     end),
-    if Resp /= ok -> Resp; true ->
-        fabric2_server:store(Db2#{security_doc := Security})
+    case Resp of
+        {ok, #{} = Db2} ->
+            fabric2_server:store(Db2#{security_doc := Security});
+        Err ->
+            Err
     end.
 
 
diff --git a/src/fabric/src/fabric2_fdb.erl b/src/fabric/src/fabric2_fdb.erl
index 1392ccd..a3dd7e2 100644
--- a/src/fabric/src/fabric2_fdb.erl
+++ b/src/fabric/src/fabric2_fdb.erl
@@ -82,16 +82,17 @@ transactional(DbName, Options, Fun) when is_binary(DbName) ->
 
 transactional(#{tx := undefined} = Db, Fun) ->
     try
-        Reopen = maps:get(reopen, Db, false),
-        Db1 = maps:remove(reopen, Db),
+        Db1 = refresh(Db),
+        Reopen = maps:get(reopen, Db1, false),
+        Db2 = maps:remove(reopen, Db1),
         LayerPrefix = case Reopen of
             true -> undefined;
-            false -> maps:get(layer_prefix, Db1)
+            false -> maps:get(layer_prefix, Db2)
         end,
         do_transaction(fun(Tx) ->
             case Reopen of
-                true -> Fun(reopen(Db1#{tx => Tx}));
-                false -> Fun(Db1#{tx => Tx})
+                true -> Fun(reopen(Db2#{tx => Tx}));
+                false -> Fun(Db2#{tx => Tx})
             end
         end, LayerPrefix)
     catch throw:{?MODULE, reopen} ->
@@ -239,6 +240,26 @@ open(#{} = Db0, Options) ->
     load_validate_doc_funs(Db3).
 
 
+refresh(#{tx := undefined, name := DbName, md_version := OldVer} = Db) ->
+    case fabric2_server:fetch(DbName) of
+        % Relying on these assumptions about the `md_version` value:
+        %  - It is bumped every time `db_version` is bumped
+        %  - Is a versionstamp, so we can check which one is newer
+        %  - If it is `not_found`, it would sort less than a binary value
+        #{md_version := Ver} = Db1 when Ver > OldVer ->
+            Db1#{
+                user_ctx := maps:get(user_ctx, Db),
+                security_fun := maps:get(security_fun, Db)
+            };
+        _ ->
+            Db
+    end;
+
+refresh(#{} = Db) ->
+    Db.
+
+
+
 reopen(#{} = OldDb) ->
     require_transaction(OldDb),
     #{
@@ -361,15 +382,16 @@ get_config(#{} = Db) ->
     end, erlfdb:wait(Future)).
 
 
-set_config(#{} = Db, ConfigKey, ConfigVal) ->
+set_config(#{} = Db0, ConfigKey, ConfigVal) ->
     #{
         tx := Tx,
         db_prefix := DbPrefix
-    } = ensure_current(Db),
+    } = Db = ensure_current(Db0),
 
     Key = erlfdb_tuple:pack({?DB_CONFIG, ConfigKey}, DbPrefix),
     erlfdb:set(Tx, Key, ConfigVal),
-    bump_db_version(Db).
+    {ok, DbVersion} = bump_db_version(Db),
+    {ok, Db#{db_version := DbVersion}}.
 
 
 get_stat(#{} = Db, StatKey) ->
@@ -927,7 +949,8 @@ bump_db_version(#{} = Db) ->
     DbVersionKey = erlfdb_tuple:pack({?DB_VERSION}, DbPrefix),
     DbVersion = fabric2_util:uuid(),
     ok = erlfdb:set(Tx, DbVersionKey, DbVersion),
-    ok = bump_metadata_version(Tx).
+    ok = bump_metadata_version(Tx),
+    {ok, DbVersion}.
 
 
 check_db_version(#{} = Db, CheckDbVersion) ->
@@ -1274,11 +1297,11 @@ ensure_current(#{} = Db0, CheckDbVersion) ->
     case maps:get(security_fun, Db2) of
         SecurityFun when is_function(SecurityFun, 2) ->
             #{security_doc := SecDoc} = Db2,
-            ok = SecurityFun(Db2, SecDoc);
+            ok = SecurityFun(Db2, SecDoc),
+            Db2#{security_fun := undefined};
         undefined ->
-            ok
-    end,
-    Db2.
+            Db2
+    end.
 
 
 is_transaction_applied(Tx) ->


[couchdb] 03/03: Update fabric2_fdb's set_config to take un-encoding values

Posted by va...@apache.org.
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

commit 44f660f157a61a87e773990abc907f8c55883160
Author: Nick Vatamaniuc <va...@apache.org>
AuthorDate: Thu Nov 14 13:03:12 2019 -0500

    Update fabric2_fdb's set_config to take un-encoding values
    
    Previously `set_config/3` needed keys and values to be transalted to binaries,
    now that is done inside the function. It's a bit more consistent as binary
    config values and encodings are better encapsulated in the `fabric2_fdb`
    module.
    
    Since `set_config` does, it made sense to update get_config as well. There, it
    turns out it was used only to load configuration setting after a db open, so
    the function was renamed to `load_config` and was made private.
---
 src/fabric/src/fabric2_db.erl  | 20 +++++++-------------
 src/fabric/src/fabric2_fdb.erl | 40 +++++++++++++++++++---------------------
 2 files changed, 26 insertions(+), 34 deletions(-)

diff --git a/src/fabric/src/fabric2_db.erl b/src/fabric/src/fabric2_db.erl
index ff5371f..d957ec9 100644
--- a/src/fabric/src/fabric2_db.erl
+++ b/src/fabric/src/fabric2_db.erl
@@ -455,32 +455,26 @@ is_users_db(DbName) when is_binary(DbName) ->
     IsAuthCache orelse IsCfgUsersDb orelse IsGlobalUsersDb.
 
 
-set_revs_limit(#{} = Db0, RevsLimit) ->
+set_revs_limit(#{} = Db0, RevsLimit) when is_integer(RevsLimit) ->
     Db1 = require_admin_check(Db0),
-    RevsLimBin = ?uint2bin(max(1, RevsLimit)),
     Resp = fabric2_fdb:transactional(Db1, fun(TxDb) ->
-        fabric2_fdb:set_config(TxDb, <<"revs_limit">>, RevsLimBin)
+        fabric2_fdb:set_config(TxDb, revs_limit, RevsLimit)
     end),
     case Resp of
-        {ok, #{} = Db2} ->
-             fabric2_server:store(Db2#{revs_limit := RevsLimit});
-        Err ->
-            Err
+        {ok, #{} = Db2} -> fabric2_server:store(Db2);
+        Err -> Err
     end.
 
 
 set_security(#{} = Db0, Security) ->
     Db1 = require_admin_check(Db0),
     ok = fabric2_util:validate_security_object(Security),
-    SecBin = ?JSON_ENCODE(Security),
     Resp = fabric2_fdb:transactional(Db1, fun(TxDb) ->
-        fabric2_fdb:set_config(TxDb, <<"security_doc">>, SecBin)
+        fabric2_fdb:set_config(TxDb, security_doc, Security)
     end),
     case Resp of
-        {ok, #{} = Db2} ->
-            fabric2_server:store(Db2#{security_doc := Security});
-        Err ->
-            Err
+        {ok, #{} = Db2} -> fabric2_server:store(Db2);
+        Err -> Err
     end.
 
 
diff --git a/src/fabric/src/fabric2_fdb.erl b/src/fabric/src/fabric2_fdb.erl
index a3dd7e2..97f0bc9 100644
--- a/src/fabric/src/fabric2_fdb.erl
+++ b/src/fabric/src/fabric2_fdb.erl
@@ -29,7 +29,6 @@
     list_dbs/4,
 
     get_info/1,
-    get_config/1,
     set_config/3,
 
     get_stat/2,
@@ -226,16 +225,7 @@ open(#{} = Db0, Options) ->
         db_options => Options1
     },
 
-    Db3 = lists:foldl(fun({Key, Val}, DbAcc) ->
-        case Key of
-            <<"uuid">> ->
-                DbAcc#{uuid => Val};
-            <<"revs_limit">> ->
-                DbAcc#{revs_limit => ?bin2uint(Val)};
-            <<"security_doc">> ->
-                DbAcc#{security_doc => ?JSON_DECODE(Val)}
-        end
-    end, Db2, get_config(Db2)),
+    Db3 = load_config(Db2),
 
     load_validate_doc_funs(Db3).
 
@@ -367,31 +357,39 @@ get_info(#{} = Db) ->
     [CProp | MProps].
 
 
-get_config(#{} = Db) ->
+load_config(#{} = Db) ->
     #{
         tx := Tx,
         db_prefix := DbPrefix
-    } = ensure_current(Db),
+    } = Db,
 
     {Start, End} = erlfdb_tuple:range({?DB_CONFIG}, DbPrefix),
     Future = erlfdb:get_range(Tx, Start, End),
 
-    lists:map(fun({K, V}) ->
+    lists:foldl(fun({K, V}, DbAcc) ->
         {?DB_CONFIG, Key} = erlfdb_tuple:unpack(K, DbPrefix),
-        {Key, V}
-    end, erlfdb:wait(Future)).
+        case Key of
+            <<"uuid">> ->  DbAcc#{uuid => V};
+            <<"revs_limit">> -> DbAcc#{revs_limit => ?bin2uint(V)};
+            <<"security_doc">> -> DbAcc#{security_doc => ?JSON_DECODE(V)}
+        end
+    end, Db, erlfdb:wait(Future)).
 
 
-set_config(#{} = Db0, ConfigKey, ConfigVal) ->
+set_config(#{} = Db0, Key, Val) when is_atom(Key) ->
     #{
         tx := Tx,
         db_prefix := DbPrefix
     } = Db = ensure_current(Db0),
-
-    Key = erlfdb_tuple:pack({?DB_CONFIG, ConfigKey}, DbPrefix),
-    erlfdb:set(Tx, Key, ConfigVal),
+    {BinKey, BinVal} = case Key of
+        uuid -> {<<"uuid">>, Val};
+        revs_limit -> {<<"revs_limit">>, ?uint2bin(max(1, Val))};
+        security_doc -> {<<"security_doc">>, ?JSON_ENCODE(Val)}
+    end,
+    DbKey = erlfdb_tuple:pack({?DB_CONFIG, BinKey}, DbPrefix),
+    erlfdb:set(Tx, DbKey, BinVal),
     {ok, DbVersion} = bump_db_version(Db),
-    {ok, Db#{db_version := DbVersion}}.
+    {ok, Db#{db_version := DbVersion, Key := Val}}.
 
 
 get_stat(#{} = Db, StatKey) ->