blob: 0598a5a4019182e6386b50073186386df3d8b287 [file] [log] [blame]
% 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(couchdb_compaction_daemon).
-include_lib("couch/include/couch_eunit.hrl").
-include_lib("couch/include/couch_db.hrl").
-define(TIMEOUT, 30000).
-define(TIMEOUT_S, ?TIMEOUT div 1000).
-ifdef(run_broken_tests).
start() ->
Ctx = test_util:start_couch(),
config:set("compaction_daemon", "check_interval", "3", false),
config:set("compaction_daemon", "min_file_size", "100000", false),
Ctx.
setup() ->
DbName = ?tempdb(),
{ok, Db} = couch_db:create(DbName, [?ADMIN_CTX]),
create_design_doc(Db),
ok = couch_db:close(Db),
DbName.
teardown(DbName) ->
Configs = config:get("compactions"),
lists:foreach(
fun({Key, _}) ->
ok = config:delete("compactions", Key, false)
end,
Configs),
couch_server:delete(DbName, [?ADMIN_CTX]),
ok.
compaction_daemon_test_() ->
{
"Compaction daemon tests",
{
setup,
fun start/0, fun test_util:stop_couch/1,
{
foreach,
fun setup/0, fun teardown/1,
[
fun should_compact_by_default_rule/1,
fun should_compact_by_dbname_rule/1
]
}
}
}.
should_compact_by_default_rule(DbName) ->
{timeout, ?TIMEOUT_S, ?_test(begin
{ok, Db} = couch_db:open_int(DbName, []),
populate(DbName, 70, 70, 200 * 1024),
{_, DbFileSize} = get_db_frag(DbName),
{_, ViewFileSize} = get_view_frag(DbName),
with_config_change(DbName, fun() ->
ok = config:set("compactions", "_default",
"[{db_fragmentation, \"70%\"}, {view_fragmentation, \"70%\"}]",
false)
end),
wait_compaction_started(DbName),
wait_compaction_finished(DbName),
with_config_change(DbName, fun() ->
ok = config:delete("compactions", "_default", false)
end),
{DbFrag2, DbFileSize2} = get_db_frag(DbName),
{ViewFrag2, ViewFileSize2} = get_view_frag(DbName),
?assert(DbFrag2 < 70),
?assert(ViewFrag2 < 70),
?assert(DbFileSize > DbFileSize2),
?assert(ViewFileSize > ViewFileSize2),
?assert(is_idle(DbName)),
ok = couch_db:close(Db)
end)}.
should_compact_by_dbname_rule(DbName) ->
{timeout, ?TIMEOUT_S, ?_test(begin
{ok, Db} = couch_db:open_int(DbName, []),
populate(DbName, 70, 70, 200 * 1024),
{_, DbFileSize} = get_db_frag(DbName),
{_, ViewFileSize} = get_view_frag(DbName),
with_config_change(DbName, fun() ->
ok = config:set("compactions", ?b2l(DbName),
"[{db_fragmentation, \"70%\"}, {view_fragmentation, \"70%\"}]",
false)
end),
wait_compaction_started(DbName),
wait_compaction_finished(DbName),
with_config_change(DbName, fun() ->
ok = config:delete("compactions", ?b2l(DbName), false)
end),
{DbFrag2, DbFileSize2} = get_db_frag(DbName),
{ViewFrag2, ViewFileSize2} = get_view_frag(DbName),
?assert(DbFrag2 < 70),
?assert(ViewFrag2 < 70),
?assert(DbFileSize > DbFileSize2),
?assert(ViewFileSize > ViewFileSize2),
?assert(is_idle(DbName)),
ok = couch_db:close(Db)
end)}.
create_design_doc(Db) ->
DDoc = couch_doc:from_json_obj({[
{<<"_id">>, <<"_design/foo">>},
{<<"language">>, <<"javascript">>},
{<<"views">>, {[
{<<"foo">>, {[
{<<"map">>, <<"function(doc) { emit(doc._id, doc); }">>}
]}},
{<<"foo2">>, {[
{<<"map">>, <<"function(doc) { emit(doc._id, doc); }">>}
]}},
{<<"foo3">>, {[
{<<"map">>, <<"function(doc) { emit(doc._id, doc); }">>}
]}}
]}}
]}),
{ok, _} = couch_db:update_docs(Db, [DDoc]),
{ok, _} = couch_db:ensure_full_commit(Db),
ok.
populate(DbName, DbFrag, ViewFrag, MinFileSize) ->
{CurDbFrag, DbFileSize} = get_db_frag(DbName),
{CurViewFrag, ViewFileSize} = get_view_frag(DbName),
populate(DbName, DbFrag, ViewFrag, MinFileSize, CurDbFrag, CurViewFrag,
lists:min([DbFileSize, ViewFileSize])).
populate(_Db, DbFrag, ViewFrag, MinFileSize, CurDbFrag, CurViewFrag, FileSize)
when CurDbFrag >= DbFrag, CurViewFrag >= ViewFrag, FileSize >= MinFileSize ->
ok;
populate(DbName, DbFrag, ViewFrag, MinFileSize, _, _, _) ->
update(DbName),
{CurDbFrag, DbFileSize} = get_db_frag(DbName),
{CurViewFrag, ViewFileSize} = get_view_frag(DbName),
populate(DbName, DbFrag, ViewFrag, MinFileSize, CurDbFrag, CurViewFrag,
lists:min([DbFileSize, ViewFileSize])).
update(DbName) ->
{ok, Db} = couch_db:open_int(DbName, []),
lists:foreach(fun(_) ->
Doc = couch_doc:from_json_obj({[{<<"_id">>, couch_uuids:new()}]}),
{ok, _} = couch_db:update_docs(Db, [Doc]),
query_view(Db#db.name)
end, lists:seq(1, 200)),
couch_db:close(Db).
db_url(DbName) ->
Addr = config:get("httpd", "bind_address", "127.0.0.1"),
Port = integer_to_list(mochiweb_socket_server:get(couch_httpd, port)),
"http://" ++ Addr ++ ":" ++ Port ++ "/" ++ ?b2l(DbName).
query_view(DbName) ->
{ok, Code, _Headers, _Body} = test_request:get(
db_url(DbName) ++ "/_design/foo/_view/foo"),
?assertEqual(200, Code).
get_db_frag(DbName) ->
{ok, Db} = couch_db:open_int(DbName, []),
{ok, Info} = couch_db:get_db_info(Db),
couch_db:close(Db),
FileSize = get_size(file, Info),
DataSize = get_size(external, Info),
{round((FileSize - DataSize) / FileSize * 100), FileSize}.
get_view_frag(DbName) ->
{ok, Db} = couch_db:open_int(DbName, []),
{ok, Info} = couch_mrview:get_info(Db, <<"_design/foo">>),
couch_db:close(Db),
FileSize = get_size(file, Info),
DataSize = get_size(external, Info),
{round((FileSize - DataSize) / FileSize * 100), FileSize}.
get_size(Kind, Info) ->
couch_util:get_nested_json_value({Info}, [sizes, Kind]).
wait_compaction_started(DbName) ->
WaitFun = fun() ->
case is_compaction_running(DbName) of
false -> wait;
true -> ok
end
end,
case test_util:wait(WaitFun, 10000) of
timeout ->
erlang:error({assertion_failed,
[{module, ?MODULE},
{line, ?LINE},
{reason, "Compaction starting timeout"}]});
_ ->
ok
end.
wait_compaction_finished(DbName) ->
WaitFun = fun() ->
case is_compaction_running(DbName) of
true -> wait;
false -> ok
end
end,
case test_util:wait(WaitFun, 10000) of
timeout ->
erlang:error({assertion_failed,
[{module, ?MODULE},
{line, ?LINE},
{reason, "Compaction timeout"}]});
_ ->
ok
end.
is_compaction_running(_DbName) ->
couch_compaction_daemon:in_progress() /= [].
is_idle(DbName) ->
{ok, Db} = couch_db:open_int(DbName, [?ADMIN_CTX]),
Monitors = couch_db:monitored_by(Db),
ok = couch_db:close(Db),
not lists:any(fun(M) -> M /= self() end, Monitors).
with_config_change(DbName, Fun) ->
Current = ets:info(couch_compaction_daemon_config, size),
Fun(),
test_util:wait(fun() ->
case ets:info(couch_compaction_daemon_config, size) == Current of
false -> ok;
true -> wait
end
end).
-endif.