You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by bb...@apache.org on 2017/03/17 19:19:43 UTC
couch commit: updated refs/heads/master to 016e1aa
Repository: couchdb-couch
Updated Branches:
refs/heads/master c50a8cda6 -> 016e1aa0e
Implement an ETS-basd couch_lru
Use a monotonicaly incrementing counter instead of `erlang:now()`. We don't
technically need to time-based functionality and just want to know relative
insertion order.
Instead of gb_tree, use an ordered_set ETS. This keep items sorted by their
update order, with most recent ones at the bottom.
An set ETS replaces the dictionary which maintains a mapping from database
names to their entry in updates table.
Interface is the same as the old couch_lru, so it is a direct swap in.
Thanks to Eric Avdey for intial version of test module.
COUCHDB-3326
Project: http://git-wip-us.apache.org/repos/asf/couchdb-couch/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-couch/commit/016e1aa0
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-couch/tree/016e1aa0
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-couch/diff/016e1aa0
Branch: refs/heads/master
Commit: 016e1aa0ef4db0bbf47a28a2cce48b85200702d6
Parents: c50a8cd
Author: Nick Vatamaniuc <va...@apache.org>
Authored: Wed Mar 15 14:06:25 2017 -0400
Committer: Benjamin Bastian <be...@gmail.com>
Committed: Fri Mar 17 12:16:40 2017 -0700
----------------------------------------------------------------------
src/couch_lru.erl | 60 +++++++++++++----------
test/couch_lru_tests.erl | 109 ++++++++++++++++++++++++++++++++++++++++++
2 files changed, 143 insertions(+), 26 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/016e1aa0/src/couch_lru.erl
----------------------------------------------------------------------
diff --git a/src/couch_lru.erl b/src/couch_lru.erl
index 84d7bee..b79286e 100644
--- a/src/couch_lru.erl
+++ b/src/couch_lru.erl
@@ -16,33 +16,39 @@
-include_lib("couch/include/couch_db.hrl").
new() ->
- {gb_trees:empty(), dict:new()}.
-
-insert(DbName, {Tree0, Dict0}) ->
- Lru = erlang:now(),
- {gb_trees:insert(Lru, DbName, Tree0), dict:store(DbName, Lru, Dict0)}.
-
-update(DbName, {Tree0, Dict0}) ->
- case dict:find(DbName, Dict0) of
- {ok, Old} ->
- New = erlang:now(),
- Tree = gb_trees:insert(New, DbName, gb_trees:delete(Old, Tree0)),
- Dict = dict:store(DbName, New, Dict0),
- {Tree, Dict};
- error ->
- % We closed this database before processing the update. Ignore
- {Tree0, Dict0}
+ Updates = ets:new(couch_lru_updates, [ordered_set]),
+ Dbs = ets:new(couch_lru_dbs, [set]),
+ {0, Updates, Dbs}.
+
+insert(DbName, {Count, Updates, Dbs}) ->
+ update(DbName, {Count, Updates, Dbs}).
+
+update(DbName, {Count, Updates, Dbs}) ->
+ case ets:lookup(Dbs, DbName) of
+ [] ->
+ true = ets:insert(Dbs, {DbName, Count});
+ [{DbName, OldCount}] ->
+ true = ets:update_element(Dbs, DbName, {2, Count}),
+ true = ets:delete(Updates, {OldCount, DbName})
+ end,
+ true = ets:insert(Updates, {{Count, DbName}}),
+ {Count + 1, Updates, Dbs}.
+
+
+close({Count, Updates, Dbs}) ->
+ case close_int(ets:next(Updates, {-1, <<>>}), Updates, Dbs) of
+ true ->
+ {true, {Count, Updates, Dbs}};
+ false ->
+ false
end.
-%% Attempt to close the oldest idle database.
-close({Tree, _} = Cache) ->
- close_int(gb_trees:next(gb_trees:iterator(Tree)), Cache).
%% internals
-close_int(none, _) ->
+close_int('$end_of_table', _Updates, _Dbs) ->
false;
-close_int({Lru, DbName, Iter}, {Tree, Dict} = Cache) ->
+close_int({_Count, DbName} = Key, Updates, Dbs) ->
case ets:update_element(couch_dbs, DbName, {#db.fd_monitor, locked}) of
true ->
[#db{main_pid = Pid} = Db] = ets:lookup(couch_dbs, DbName),
@@ -50,14 +56,16 @@ close_int({Lru, DbName, Iter}, {Tree, Dict} = Cache) ->
true = ets:delete(couch_dbs, DbName),
true = ets:delete(couch_dbs_pid_to_name, Pid),
exit(Pid, kill),
- {true, {gb_trees:delete(Lru, Tree), dict:erase(DbName, Dict)}};
+ true = ets:delete(Updates, Key),
+ true = ets:delete(Dbs, DbName),
+ true;
false ->
true = ets:update_element(couch_dbs, DbName, {#db.fd_monitor, nil}),
couch_stats:increment_counter([couchdb, couch_server, lru_skip]),
- close_int(gb_trees:next(Iter), update(DbName, Cache))
+ close_int(ets:next(Updates, Key), Updates, Dbs)
end;
false ->
- NewTree = gb_trees:delete(Lru, Tree),
- NewIter = gb_trees:iterator(NewTree),
- close_int(gb_trees:next(NewIter), {NewTree, dict:erase(DbName, Dict)})
+ true = ets:delete(Updates, Key),
+ true = ets:delete(Dbs, DbName),
+ close_int(ets:next(Updates, Key), Updates, Dbs)
end.
http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/016e1aa0/test/couch_lru_tests.erl
----------------------------------------------------------------------
diff --git a/test/couch_lru_tests.erl b/test/couch_lru_tests.erl
new file mode 100644
index 0000000..1559835
--- /dev/null
+++ b/test/couch_lru_tests.erl
@@ -0,0 +1,109 @@
+% 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_lru_tests).
+
+-include_lib("couch/include/couch_eunit.hrl").
+-include_lib("couch/include/couch_db.hrl").
+
+
+setup() ->
+ ok = meck:new(couch_db, [passthrough]),
+ ets:new(couch_dbs, [set, public, named_table, {keypos, #db.name}]),
+ ets:new(couch_dbs_pid_to_name, [set, public, named_table]),
+ couch_lru:new().
+
+teardown(_) ->
+ ets:delete(couch_dbs),
+ ets:delete(couch_dbs_pid_to_name),
+ (catch meck:unload(couch_db)).
+
+new_test_() ->
+ {setup,
+ fun() -> couch_lru:new() end,
+ fun(Lru) ->
+ ?_assertMatch({0, _, _}, Lru)
+ end
+ }.
+
+insert_test_() ->
+ {setup,
+ fun() -> couch_lru:new() end,
+ fun(Lru) ->
+ Key = <<"test">>,
+ {1, Updates, Dbs} = couch_lru:insert(Key, Lru),
+ [
+ ?_assertEqual(1, ets_size(Dbs)),
+ ?_assert(ets:member(Dbs, Key)),
+ ?_assertEqual(1, ets_size(Updates)),
+ ?_assert(ets:member(Updates, {0, Key}))
+ ]
+ end
+ }.
+
+insert_same_test_() ->
+ {setup,
+ fun() -> couch_lru:new() end,
+ fun(Lru) ->
+ Key = <<"test">>,
+ Lru1 = {1, Updates, Dbs} = couch_lru:insert(Key, Lru),
+ {2, Updates, Dbs} = couch_lru:insert(Key, Lru1),
+ [
+ ?_assertEqual(1, ets_size(Dbs)),
+ ?_assert(ets:member(Dbs, Key)),
+ ?_assertEqual(1, ets_size(Updates)),
+ ?_assert(ets:member(Updates, {1, Key}))
+ ]
+ end
+ }.
+
+update_test_() ->
+ {setup,
+ fun() -> couch_lru:new() end,
+ fun(Lru) ->
+ Key = <<"test">>,
+ Lru1 = {1, Updates, Dbs} = couch_lru:update(Key, Lru),
+ {2, Updates, Dbs} = couch_lru:update(Key, Lru1),
+ [
+ ?_assertEqual(1, ets_size(Dbs)),
+ ?_assert(ets:member(Dbs, Key)),
+ ?_assertEqual(1, ets_size(Updates)),
+ ?_assert(ets:member(Updates, {1, Key}))
+ ]
+ end
+ }.
+
+close_test_() ->
+ {setup,
+ fun setup/0,
+ fun teardown/1,
+ fun(Lru) ->
+ ok = meck:expect(couch_db, is_idle, 1, true),
+ {ok, Lru1} = add_record(Lru, <<"test1">>, c:pid(0, 1001, 0)),
+ {ok, Lru2} = add_record(Lru1, <<"test2">>, c:pid(0, 2001, 0)),
+ {true, {2, Updates, Dbs}} = couch_lru:close(Lru2),
+ [
+ ?_assertEqual(1, ets_size(Dbs)),
+ ?_assert(ets:member(Dbs, <<"test2">>)),
+ ?_assertEqual(1, ets_size(Updates)),
+ ?_assert(ets:member(Updates, {1, <<"test2">>}))
+ ]
+ end
+ }.
+
+add_record(Lru, Key, Pid) ->
+ true = ets:insert(couch_dbs, #db{name = Key, main_pid = Pid}),
+ true = ets:insert(couch_dbs_pid_to_name, {Pid, Key}),
+ {ok, couch_lru:insert(Key, Lru)}.
+
+ets_size(Ets) ->
+ proplists:get_value(size, ets:info(Ets)).