diff options
author | Nick Vatamaniuc <vatamane@apache.org> | 2017-05-17 17:30:20 -0400 |
---|---|---|
committer | Nick Vatamaniuc <nickva@users.noreply.github.com> | 2017-05-19 11:10:24 -0400 |
commit | fb6faec9b91b6593c3c10e35728a2f9cfc623a43 (patch) | |
tree | b813ca2d060708ec90bdd788efbf22e707040ec9 | |
parent | 1fa3f58b5733031b2e73e68f921e364ff6533689 (diff) | |
download | couchdb-fb6faec9b91b6593c3c10e35728a2f9cfc623a43.tar.gz |
Revert couch_lru to use gb_trees
Recently couch_lru was changed to use ets tables.
During eprof profiling it showed improved performance however recently in a
larger test with more concurrent updates and 5000 max dbs open it showed a
significant degradation compared to the previous (gb_tree-based) version.
GH Issue #528
-rw-r--r-- | src/couch/src/couch_lru.erl | 62 | ||||
-rw-r--r-- | src/couch/test/couch_lru_tests.erl | 109 |
2 files changed, 27 insertions, 144 deletions
diff --git a/src/couch/src/couch_lru.erl b/src/couch/src/couch_lru.erl index b79286ee0..b58a623d6 100644 --- a/src/couch/src/couch_lru.erl +++ b/src/couch/src/couch_lru.erl @@ -16,39 +16,33 @@ -include_lib("couch/include/couch_db.hrl"). new() -> - 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 + {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} end. +%% Attempt to close the oldest idle database. +close({Tree, _} = Cache) -> + close_int(gb_trees:next(gb_trees:iterator(Tree)), Cache). %% internals -close_int('$end_of_table', _Updates, _Dbs) -> +close_int(none, _) -> false; -close_int({_Count, DbName} = Key, Updates, Dbs) -> +close_int({Lru, DbName, Iter}, {Tree, Dict} = Cache) -> case ets:update_element(couch_dbs, DbName, {#db.fd_monitor, locked}) of true -> [#db{main_pid = Pid} = Db] = ets:lookup(couch_dbs, DbName), @@ -56,16 +50,14 @@ close_int({_Count, DbName} = Key, Updates, Dbs) -> true = ets:delete(couch_dbs, DbName), true = ets:delete(couch_dbs_pid_to_name, Pid), exit(Pid, kill), - true = ets:delete(Updates, Key), - true = ets:delete(Dbs, DbName), - true; + {true, {gb_trees:delete(Lru, Tree), dict:erase(DbName, Dict)}}; false -> true = ets:update_element(couch_dbs, DbName, {#db.fd_monitor, nil}), couch_stats:increment_counter([couchdb, couch_server, lru_skip]), - close_int(ets:next(Updates, Key), Updates, Dbs) + close_int(gb_trees:next(Iter), update(DbName, Cache)) end; false -> - true = ets:delete(Updates, Key), - true = ets:delete(Dbs, DbName), - close_int(ets:next(Updates, Key), Updates, Dbs) - end. + NewTree = gb_trees:delete(Lru, Tree), + NewIter = gb_trees:iterator(NewTree), + close_int(gb_trees:next(NewIter), {NewTree, dict:erase(DbName, Dict)}) +end. diff --git a/src/couch/test/couch_lru_tests.erl b/src/couch/test/couch_lru_tests.erl deleted file mode 100644 index 15598358f..000000000 --- a/src/couch/test/couch_lru_tests.erl +++ /dev/null @@ -1,109 +0,0 @@ -% 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)). |