diff options
author | Nick Vatamaniuc <vatamane@apache.org> | 2019-10-17 17:43:10 -0400 |
---|---|---|
committer | Nick Vatamaniuc <nickva@users.noreply.github.com> | 2019-10-18 15:25:32 -0400 |
commit | f3d572caf112598d12d705f98dffb8cdbb2bec97 (patch) | |
tree | 7fc9f5271b287d4993b53537bace794a217002a0 | |
parent | bfb986f718b6e4c9f1b50bc63e9d2a10d19ebdb5 (diff) | |
download | couchdb-f3d572caf112598d12d705f98dffb8cdbb2bec97.tar.gz |
Take better advantage of metadata version key feature
FDB's metadata version key allows more efficient metadata invalidation
(see https://github.com/apple/foundationdb/pull/1213). To take advantage of
that feature update the caching logic to check the metadata version first, and
if it is current, skip checking the db version altogether.
When db version is bumped we now update the metadata version as well.
There is a bit of a subtlety when the metadata version is stale. In that case
we check the db version, and if that is current, we still don't reopen the
database, instead we continue with the transaction. Then, after the transaction
succeeds, we update the cached metadata version for that db handle. Next client
would get the updated db metadata, it will be current, and they won't need to
check the db version. If the db version is stale as well, then we throw a
`reopen` exception and the handle gets removed from the cache and reopened.
Note: this commit doesn't actually use the new metadata version key, it still
uses the old plain key. That update will be a separate commit where we also start
setting a new API version (610) and will only work on FDB version 6.1.x
-rw-r--r-- | src/fabric/include/fabric2.hrl | 2 | ||||
-rw-r--r-- | src/fabric/src/fabric2_fdb.erl | 110 | ||||
-rw-r--r-- | src/fabric/test/fabric2_db_misc_tests.erl | 56 |
3 files changed, 137 insertions, 31 deletions
diff --git a/src/fabric/include/fabric2.hrl b/src/fabric/include/fabric2.hrl index 3e224987d..fe11e6b8d 100644 --- a/src/fabric/include/fabric2.hrl +++ b/src/fabric/include/fabric2.hrl @@ -59,8 +59,10 @@ -define(PDICT_DB_KEY, '$fabric_db_handle'). -define(PDICT_LAYER_CACHE, '$fabric_layer_id'). -define(PDICT_CHECKED_DB_IS_CURRENT, '$fabric_checked_db_is_current'). +-define(PDICT_CHECKED_MD_IS_CURRENT, '$fabric_checked_md_is_current'). -define(PDICT_TX_ID_KEY, '$fabric_tx_id'). -define(PDICT_TX_RES_KEY, '$fabric_tx_result'). +-define(PDICT_ON_COMMIT_FUN, '$fabric_on_commit_fun'). -define(COMMIT_UNKNOWN_RESULT, 1021). diff --git a/src/fabric/src/fabric2_fdb.erl b/src/fabric/src/fabric2_fdb.erl index 0f55d9175..2ccde1cb4 100644 --- a/src/fabric/src/fabric2_fdb.erl +++ b/src/fabric/src/fabric2_fdb.erl @@ -117,7 +117,11 @@ do_transaction(Fun, LayerPrefix) when is_function(Fun, 1) -> true -> get_previous_transaction_result(); false -> - execute_transaction(Tx, Fun, LayerPrefix) + try + execute_transaction(Tx, Fun, LayerPrefix) + after + erase({?PDICT_ON_COMMIT_FUN, Tx}) + end end end) after @@ -864,6 +868,31 @@ bump_metadata_version(Tx) -> erlfdb:set_versionstamped_value(Tx, ?METADATA_VERSION_KEY, <<0:112>>). +check_metadata_version(#{} = Db) -> + #{ + tx := Tx, + layer_prefix := LayerPrefix, + name := DbName, + md_version := Version + } = Db, + + AlreadyChecked = get(?PDICT_CHECKED_MD_IS_CURRENT), + if AlreadyChecked == true -> {current, Db}; true -> + case erlfdb:wait(erlfdb:get_ss(Tx, ?METADATA_VERSION_KEY)) of + Version -> + put(?PDICT_CHECKED_MD_IS_CURRENT, true), + % We want to set a read conflict on the db version as we'd want + % to to conflict with any writes to this particular db + DbPrefix = erlfdb_tuple:pack({?DBS, DbName}, LayerPrefix), + DbVersionKey = erlfdb_tuple:pack({?DB_VERSION}, DbPrefix), + erlfdb:add_read_conflict_key(Tx, DbVersionKey), + {current, Db}; + NewVersion -> + {stale, Db#{md_version := NewVersion}} + end + end. + + bump_db_version(#{} = Db) -> #{ tx := Tx, @@ -872,7 +901,30 @@ bump_db_version(#{} = Db) -> DbVersionKey = erlfdb_tuple:pack({?DB_VERSION}, DbPrefix), DbVersion = fabric2_util:uuid(), - ok = erlfdb:set(Tx, DbVersionKey, DbVersion). + ok = erlfdb:set(Tx, DbVersionKey, DbVersion), + ok = bump_metadata_version(Tx). + + +check_db_version(#{} = Db, CheckDbVersion) -> + #{ + tx := Tx, + db_prefix := DbPrefix, + db_version := DbVersion + } = Db, + + AlreadyChecked = get(?PDICT_CHECKED_DB_IS_CURRENT), + if not CheckDbVersion orelse AlreadyChecked == true -> Db; true -> + DbVersionKey = erlfdb_tuple:pack({?DB_VERSION}, DbPrefix), + 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; + _NewDBVersion -> + fabric2_server:remove(maps:get(name, Db)), + throw({?MODULE, reopen}) + end + end. write_doc_body(#{} = Db0, #doc{} = Doc) -> @@ -1171,34 +1223,9 @@ ensure_current(Db) -> ensure_current(#{} = Db, CheckDbVersion) -> require_transaction(Db), - - #{ - tx := Tx, - md_version := MetaDataVersion - } = Db, - - case erlfdb:wait(erlfdb:get(Tx, ?METADATA_VERSION_KEY)) of - MetaDataVersion -> Db; - _NewVersion -> throw({?MODULE, reopen}) - end, - - AlreadyChecked = get(?PDICT_CHECKED_DB_IS_CURRENT), - if not CheckDbVersion orelse AlreadyChecked == true -> Db; true -> - #{ - db_prefix := DbPrefix, - db_version := DbVersion - } = Db, - - DbVersionKey = erlfdb_tuple:pack({?DB_VERSION}, DbPrefix), - - case erlfdb:wait(erlfdb:get(Tx, DbVersionKey)) of - DbVersion -> - put(?PDICT_CHECKED_DB_IS_CURRENT, true), - Db; - _NewDBVersion -> - fabric2_server:remove(maps:get(name, Db)), - throw({?MODULE, reopen}) - end + case check_metadata_version(Db) of + {current, Db1} -> Db1; + {stale, Db1} -> check_db_version(Db1, CheckDbVersion) end. @@ -1222,12 +1249,14 @@ execute_transaction(Tx, Fun, LayerPrefix) -> erlfdb:set(Tx, get_transaction_id(Tx, LayerPrefix), <<>>), put(?PDICT_TX_RES_KEY, Result) end, + ok = run_on_commit_fun(Tx), Result. clear_transaction() -> fabric2_txids:remove(get(?PDICT_TX_ID_KEY)), erase(?PDICT_CHECKED_DB_IS_CURRENT), + erase(?PDICT_CHECKED_MD_IS_CURRENT), erase(?PDICT_TX_ID_KEY), erase(?PDICT_TX_RES_KEY). @@ -1259,3 +1288,24 @@ new_versionstamp(Tx) -> TxId = erlfdb:get_next_tx_id(Tx), {versionstamp, 16#FFFFFFFFFFFFFFFF, 16#FFFF, TxId}. + +on_commit(Tx, Fun) when is_function(Fun, 0) -> + % Here we rely on Tx objects matching. However they contain a nif resource + % object. Before Erlang 20.0 those would have been represented as empty + % binaries and would have compared equal to each other. See + % http://erlang.org/doc/man/erl_nif.html for more info. We assume we run on + % Erlang 20+ here and don't worry about that anymore. + case get({?PDICT_ON_COMMIT_FUN, Tx}) of + undefined -> put({?PDICT_ON_COMMIT_FUN, Tx}, Fun); + _ -> error({?MODULE, on_commit_function_already_set}) + end. + + +run_on_commit_fun(Tx) -> + case get({?PDICT_ON_COMMIT_FUN, Tx}) of + undefined -> + ok; + Fun when is_function(Fun, 0) -> + Fun(), + ok + end. diff --git a/src/fabric/test/fabric2_db_misc_tests.erl b/src/fabric/test/fabric2_db_misc_tests.erl index 8e6405632..913b6aa98 100644 --- a/src/fabric/test/fabric2_db_misc_tests.erl +++ b/src/fabric/test/fabric2_db_misc_tests.erl @@ -16,6 +16,7 @@ -include_lib("couch/include/couch_db.hrl"). -include_lib("couch/include/couch_eunit.hrl"). -include_lib("eunit/include/eunit.hrl"). +-include("fabric2.hrl"). -define(TDEF(A), {atom_to_list(A), fun A/1}). @@ -34,7 +35,9 @@ misc_test_() -> fun set_revs_limit/1, fun set_security/1, fun is_system_db/1, - fun ensure_full_commit/1 + fun ensure_full_commit/1, + fun metadata_bump/1, + fun db_version_bump/1 ]} } }. @@ -111,3 +114,54 @@ is_system_db({DbName, Db, _}) -> ensure_full_commit({_, Db, _}) -> ?assertEqual({ok, 0}, fabric2_db:ensure_full_commit(Db)), ?assertEqual({ok, 0}, fabric2_db:ensure_full_commit(Db, 5)). + + +metadata_bump({DbName, _, _}) -> + % Call open again here to make sure we have a version in the cache + % as we'll be checking if that version gets its metadata bumped + {ok, Db} = fabric2_db:open(DbName, [{user_ctx, ?ADMIN_USER}]), + + % Emulate a remote client bumping the metadataversion + {ok, Fdb} = application:get_env(fabric, db), + erlfdb:transactional(Fdb, fun(Tx) -> + erlfdb:set_versionstamped_value(Tx, ?METADATA_VERSION_KEY, <<0:112>>) + end), + NewMDVersion = erlfdb:transactional(Fdb, fun(Tx) -> + erlfdb:wait(erlfdb:get(Tx, ?METADATA_VERSION_KEY)) + end), + + % 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 + ?assertMatch(#{md_version := NewMDVersion}, fabric2_server:fetch(DbName)). + + +db_version_bump({DbName, _, _}) -> + % Call open again here to make sure we have a version in the cache + % as we'll be checking if that version gets its metadata bumped + {ok, Db} = fabric2_db:open(DbName, [{user_ctx, ?ADMIN_USER}]), + + % Emulate a remote client bumping db version. We don't go through the + % regular db open + update security doc or something like that to make sure + % we don't touch the local cache + #{db_prefix := DbPrefix} = Db, + DbVersionKey = erlfdb_tuple:pack({?DB_VERSION}, DbPrefix), + {ok, Fdb} = application:get_env(fabric, db), + NewDbVersion = fabric2_util:uuid(), + erlfdb:transactional(Fdb, fun(Tx) -> + erlfdb:set(Tx, DbVersionKey, NewDbVersion), + erlfdb:set_versionstamped_value(Tx, ?METADATA_VERSION_KEY, <<0:112>>) + end), + + % Perform a random operation which calls ensure_current + {ok, _} = fabric2_db:get_db_info(Db), + + % After previous operation, the cache should have been cleared + ?assertMatch(undefined, fabric2_server:fetch(DbName)), + + % Call open again and check that we have the latest db version + {ok, Db2} = fabric2_db:open(DbName, [{user_ctx, ?ADMIN_USER}]), + + % Check that db handle in the cache got the new metadata version + ?assertMatch(#{db_version := NewDbVersion}, Db2). |