Skip to content

Commit 912b00c

Browse files
committed
Improve index cleanup
Fix these issues: * Index cleanup triggered by smoosh only cleaned view indexes and purge checkpoints, make sure to also clean nouveau and search indexes and checkpoints. * `/_search_cleanup` endpoint only cleaned indexes on the coordinator node, despite being a clustered fabric endpoint. Fix it so it cleans indexes on other nodes as well as most users would expect. * Nouveau cleanup didn't clean purge checkpoints, so make it do so. Use the mrview purge checkpoint strategy for all indexes. This improves dreyfus logic to avoid traversing internal disk paths from clouseau. * Make `_view_cleanup` clean all the index types. This is a bit dirty, but it may be better than adding another cleanup `_index_cleanup` API. Left `_search_cleanup` and `_nouveau_cleanup` APIs as is for now. Some optimizations: * For each index clean request fetch ddocs once. Calculate signatures and then call the remote node cleanup logic with them. This avoids fetching design documents multiple times or sending all of them to the worker nodes. This is what Nouveau is doing so stick with that nice pattern. * Use erpc for remote calls. Our Erlang version is high enough (25+) to use the multple requests pattern from erpc. This is more compact than rexi. The absolute timeout pattern makes it simpler to have a global timeout for the whole request. Cleanups: * Make purge checkpoint fetching and cleanup more uniform. Use common utility logic in `couch_index_util` for all indexes. * Make index cleanup similar between all three indexes. Use the same erpc pattern for all of them. * Move index cleanup functions from fabric.erl to its own module -- fabric_index_cleanup.erl * Rename fabric function names more uniform and clearly indicate if it cleans indexes on all nodes or just the current node. * Add the db name to dreyfus `#index{}` record when it initializes so it matches Nouveau.
1 parent 9dee10d commit 912b00c

21 files changed

+369
-256
lines changed

src/couch_index/src/couch_index_util.erl

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
-export([root_dir/0, index_dir/2, index_file/3]).
1616
-export([load_doc/3, sort_lib/1, hexsig/1]).
17+
-export([get_purge_checkpoints/2, cleanup_purges/3]).
1718

1819
-include_lib("couch/include/couch_db.hrl").
1920

@@ -72,3 +73,49 @@ sort_lib([{LName, LCode} | Rest], LAcc) ->
7273

7374
hexsig(Sig) ->
7475
couch_util:to_hex(Sig).
76+
77+
% Helper function for indexes to get their purge checkpoints as signatures.
78+
%
79+
get_purge_checkpoints(DbName, Type) when is_binary(DbName), is_binary(Type) ->
80+
couch_util:with_db(DbName, fun(Db) -> get_purge_checkpoints(Db, Type) end);
81+
get_purge_checkpoints(Db, Type) when is_binary(Type) ->
82+
Prefix = <<?LOCAL_DOC_PREFIX, "purge-", Type:(byte_size(Type))/binary, "-">>,
83+
PrefixSize = byte_size(Prefix),
84+
FoldFun = fun(#doc{id = Id}, Acc) ->
85+
case Id of
86+
<<Prefix:PrefixSize/binary, Sig/binary>> -> {ok, Acc#{Sig => Id}};
87+
_ -> {stop, Acc}
88+
end
89+
end,
90+
Opts = [{start_key, Prefix}],
91+
{ok, Signatures = #{}} = couch_db:fold_local_docs(Db, FoldFun, #{}, Opts),
92+
Signatures.
93+
94+
% Helper functions for indexes to clean their purge checkpoints.
95+
%
96+
cleanup_purges(DbName, #{} = Sigs, #{} = Checkpoints) when is_binary(DbName) ->
97+
couch_util:with_db(DbName, fun(Db) ->
98+
cleanup_purges(Db, Sigs, Checkpoints)
99+
end);
100+
cleanup_purges(Db, #{} = Sigs, #{} = CheckpointsMap) ->
101+
InactiveMap = maps:without(maps:keys(Sigs), CheckpointsMap),
102+
InactiveCheckpoints = maps:values(InactiveMap),
103+
DeleteFun = fun(DocId) -> delete_checkpoint(Db, DocId) end,
104+
lists:foreach(DeleteFun, InactiveCheckpoints).
105+
106+
delete_checkpoint(Db, DocId) ->
107+
DbName = couch_db:name(Db),
108+
LogMsg = "~p : deleting inactive purge checkpoint ~s : ~s",
109+
couch_log:debug(LogMsg, [?MODULE, DbName, DocId]),
110+
try couch_db:open_doc(Db, DocId, []) of
111+
{ok, Doc = #doc{}} ->
112+
Deleted = Doc#doc{deleted = true, body = {[]}},
113+
couch_db:update_doc(Db, Deleted, [?ADMIN_CTX]);
114+
{not_found, _} ->
115+
ok
116+
catch
117+
Tag:Error ->
118+
ErrLog = "~p : error deleting checkpoint ~s : ~s error: ~p:~p",
119+
couch_log:error(ErrLog, [?MODULE, DbName, DocId, Tag, Error]),
120+
ok
121+
end.

src/couch_mrview/src/couch_mrview_cleanup.erl

Lines changed: 21 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,9 @@
1414

1515
-export([
1616
run/1,
17-
cleanup_purges/3,
18-
cleanup_indices/2
17+
cleanup/2
1918
]).
2019

21-
-include_lib("couch/include/couch_db.hrl").
22-
2320
run(Db) ->
2421
Indices = couch_mrview_util:get_index_files(Db),
2522
Checkpoints = couch_mrview_util:get_purge_checkpoints(Db),
@@ -28,15 +25,26 @@ run(Db) ->
2825
ok = cleanup_purges(Db1, Sigs, Checkpoints),
2926
ok = cleanup_indices(Sigs, Indices).
3027

31-
cleanup_purges(DbName, Sigs, Checkpoints) when is_binary(DbName) ->
32-
couch_util:with_db(DbName, fun(Db) ->
33-
cleanup_purges(Db, Sigs, Checkpoints)
34-
end);
35-
cleanup_purges(Db, #{} = Sigs, #{} = CheckpointsMap) ->
36-
InactiveMap = maps:without(maps:keys(Sigs), CheckpointsMap),
37-
InactiveCheckpoints = maps:values(InactiveMap),
38-
DeleteFun = fun(DocId) -> delete_checkpoint(Db, DocId) end,
39-
lists:foreach(DeleteFun, InactiveCheckpoints).
28+
% erpc endpoint for fabric_index_cleanup:cleanup_indexes/2
29+
%
30+
cleanup(Dbs, #{} = Sigs) ->
31+
try
32+
lists:foreach(
33+
fun(Db) ->
34+
Indices = couch_mrview_util:get_index_files(Db),
35+
Checkpoints = couch_mrview_util:get_purge_checkpoints(Db),
36+
ok = cleanup_purges(Db, Sigs, Checkpoints),
37+
ok = cleanup_indices(Sigs, Indices)
38+
end,
39+
Dbs
40+
)
41+
catch
42+
error:database_does_not_exist ->
43+
ok
44+
end.
45+
46+
cleanup_purges(Db, Sigs, Checkpoints) ->
47+
couch_index_util:cleanup_purges(Db, Sigs, Checkpoints).
4048

4149
cleanup_indices(#{} = Sigs, #{} = IndexMap) ->
4250
Fun = fun(_, Files) -> lists:foreach(fun delete_file/1, Files) end,
@@ -54,20 +62,3 @@ delete_file(File) ->
5462
couch_log:error(ErrLog, [?MODULE, File, Tag, Error]),
5563
ok
5664
end.
57-
58-
delete_checkpoint(Db, DocId) ->
59-
DbName = couch_db:name(Db),
60-
LogMsg = "~p : deleting inactive purge checkpoint ~s : ~s",
61-
couch_log:debug(LogMsg, [?MODULE, DbName, DocId]),
62-
try couch_db:open_doc(Db, DocId, []) of
63-
{ok, Doc = #doc{}} ->
64-
Deleted = Doc#doc{deleted = true, body = {[]}},
65-
couch_db:update_doc(Db, Deleted, [?ADMIN_CTX]);
66-
{not_found, _} ->
67-
ok
68-
catch
69-
Tag:Error ->
70-
ErrLog = "~p : error deleting checkpoint ~s : ~s error: ~p:~p",
71-
couch_log:error(ErrLog, [?MODULE, DbName, DocId, Tag, Error]),
72-
ok
73-
end.

src/couch_mrview/src/couch_mrview_util.erl

Lines changed: 17 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
-export([get_local_purge_doc_id/1, get_value_from_options/2]).
1717
-export([verify_view_filename/1, get_signature_from_filename/1]).
1818
-export([get_signatures/1, get_purge_checkpoints/1, get_index_files/1]).
19+
-export([get_signatures_from_ddocs/2]).
1920
-export([ddoc_to_mrst/2, init_state/4, reset_index/3]).
2021
-export([make_header/1]).
2122
-export([index_file/2, compaction_file/2, open_file/1]).
@@ -94,40 +95,35 @@ get_signatures(DbName) when is_binary(DbName) ->
9495
couch_util:with_db(DbName, fun get_signatures/1);
9596
get_signatures(Db) ->
9697
DbName = couch_db:name(Db),
97-
% get_design_docs/1 returns ejson for clustered shards, and
98-
% #full_doc_info{}'s for other cases.
9998
{ok, DDocs} = couch_db:get_design_docs(Db),
99+
% get_design_docs/1 returns ejson for clustered shards, and
100+
% #full_doc_info{}'s for other cases. Both are transformed to #doc{} records
100101
FoldFun = fun
101102
({[_ | _]} = EJsonDoc, Acc) ->
102103
Doc = couch_doc:from_json_obj(EJsonDoc),
103-
{ok, Mrst} = ddoc_to_mrst(DbName, Doc),
104-
Sig = couch_util:to_hex_bin(Mrst#mrst.sig),
105-
Acc#{Sig => true};
104+
[Doc | Acc];
106105
(#full_doc_info{} = FDI, Acc) ->
107106
{ok, Doc} = couch_db:open_doc_int(Db, FDI, [ejson_body]),
108-
{ok, Mrst} = ddoc_to_mrst(DbName, Doc),
109-
Sig = couch_util:to_hex_bin(Mrst#mrst.sig),
110-
Acc#{Sig => true}
107+
[Doc | Acc]
108+
end,
109+
DDocs1 = lists:foldl(FoldFun, [], DDocs),
110+
get_signatures_from_ddocs(DbName, DDocs1).
111+
112+
% From a list of design #doc{} records returns signatures map: #{Sig => true}
113+
%
114+
get_signatures_from_ddocs(DbName, DDocs) when is_list(DDocs) ->
115+
FoldFun = fun(#doc{} = Doc, Acc) ->
116+
{ok, Mrst} = ddoc_to_mrst(DbName, Doc),
117+
Sig = couch_util:to_hex_bin(Mrst#mrst.sig),
118+
Acc#{Sig => true}
111119
end,
112120
lists:foldl(FoldFun, #{}, DDocs).
113121

114122
% Returns a map of `Sig => DocId` elements for all the purge view
115123
% checkpoint docs. Sig is a hex-encoded binary.
116124
%
117-
get_purge_checkpoints(DbName) when is_binary(DbName) ->
118-
couch_util:with_db(DbName, fun get_purge_checkpoints/1);
119125
get_purge_checkpoints(Db) ->
120-
FoldFun = fun(#doc{id = Id}, Acc) ->
121-
case Id of
122-
<<?LOCAL_DOC_PREFIX, "purge-mrview-", Sig/binary>> ->
123-
{ok, Acc#{Sig => Id}};
124-
_ ->
125-
{stop, Acc}
126-
end
127-
end,
128-
Opts = [{start_key, <<?LOCAL_DOC_PREFIX, "purge-mrview-">>}],
129-
{ok, Signatures = #{}} = couch_db:fold_local_docs(Db, FoldFun, #{}, Opts),
130-
Signatures.
126+
couch_index_util:get_purge_checkpoints(Db, <<"mrview">>).
131127

132128
% Returns a map of `Sig => [FilePath, ...]` elements. Sig is a hex-encoded
133129
% binary and FilePaths are lists as they intended to be passed to couch_file

src/dreyfus/src/clouseau_rpc.erl

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -262,10 +262,17 @@ rename(DbName) ->
262262
%% and an analyzer represented in a Javascript function in a design document.
263263
%% `Sig` is used to check if an index description is changed,
264264
%% and the index needs to be reconstructed.
265-
-spec cleanup(DbName :: string_as_binary(_), ActiveSigs :: [sig()]) ->
265+
-spec cleanup(DbName :: string_as_binary(_), SigList :: list() | SigMap :: #{sig() => true}) ->
266266
ok.
267267

268-
cleanup(DbName, ActiveSigs) ->
268+
% Compatibility clause to help when running search index cleanup during
269+
% a mixed cluster state. Remove after version 3.6
270+
%
271+
cleanup(DbName, SigList) when is_list(SigList) ->
272+
SigMap = #{Sig => true || Sig <- SigList},
273+
cleanup(DbName, SigMap);
274+
cleanup(DbName, #{} = SigMap) ->
275+
ActiveSigs = maps:keys(SigMap),
269276
gen_server:cast({cleanup, clouseau()}, {cleanup, DbName, ActiveSigs}).
270277

271278
%% a binary with value <<"tokens">>

src/dreyfus/src/dreyfus_fabric_cleanup.erl

Lines changed: 40 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -14,99 +14,50 @@
1414

1515
-module(dreyfus_fabric_cleanup).
1616

17-
-include("dreyfus.hrl").
18-
-include_lib("mem3/include/mem3.hrl").
19-
-include_lib("couch/include/couch_db.hrl").
20-
21-
-export([go/1]).
17+
-export([go/1, go_local/3]).
2218

2319
go(DbName) ->
24-
DesignDocs =
25-
case fabric:design_docs(DbName) of
26-
{ok, DDocs} when is_list(DDocs) ->
27-
DDocs;
28-
Else ->
29-
couch_log:debug("Invalid design docs: ~p~n", [Else]),
30-
[]
31-
end,
32-
ActiveSigs = lists:usort(
33-
lists:flatmap(
34-
fun active_sigs/1,
35-
[couch_doc:from_json_obj(DD) || DD <- DesignDocs]
36-
)
37-
),
38-
cleanup_local_purge_doc(DbName, ActiveSigs),
39-
clouseau_rpc:cleanup(DbName, ActiveSigs),
40-
ok.
20+
case fabric_util:get_design_doc_records(DbName) of
21+
{ok, DDocs} when is_list(DDocs) ->
22+
Sigs = dreyfus_util:get_signatures_from_ddocs(DbName, DDocs),
23+
Shards = mem3:shards(DbName),
24+
ByNode = maps:groups_from_list(fun mem3:node/1, fun mem3:name/1, Shards),
25+
Fun = fun(Node, Dbs, Acc) ->
26+
erpc:send_request(Node, ?MODULE, go_local, [DbName, Dbs, Sigs], Node, Acc)
27+
end,
28+
Reqs = maps:fold(Fun, erpc:reqids_new(), ByNode),
29+
recv(DbName, Reqs, fabric_util:abs_request_timeout());
30+
Error ->
31+
couch_log:error("~p : error fetching ddocs db:~p ~p", [?MODULE, DbName, Error]),
32+
Error
33+
end.
4134

42-
active_sigs(#doc{body = {Fields}} = Doc) ->
35+
% erpc endpoint for go/1 and fabric_index_cleanup:cleanup_indexes/2
36+
%
37+
go_local(DbName, Dbs, #{} = Sigs) ->
4338
try
44-
{RawIndexes} = couch_util:get_value(<<"indexes">>, Fields, {[]}),
45-
{IndexNames, _} = lists:unzip(RawIndexes),
46-
[
47-
begin
48-
{ok, Index} = dreyfus_index:design_doc_to_index(Doc, IndexName),
49-
Index#index.sig
50-
end
51-
|| IndexName <- IndexNames
52-
]
39+
lists:foreach(
40+
fun(Db) ->
41+
Checkpoints = dreyfus_util:get_purge_checkpoints(Db),
42+
ok = couch_index_util:cleanup_purges(Db, Sigs, Checkpoints)
43+
end,
44+
Dbs
45+
),
46+
clouseau_rpc:cleanup(DbName, Sigs),
47+
ok
5348
catch
54-
error:{badmatch, _Error} ->
55-
[]
49+
error:database_does_not_exist ->
50+
ok
5651
end.
5752

58-
cleanup_local_purge_doc(DbName, ActiveSigs) ->
59-
{ok, BaseDir} = clouseau_rpc:get_root_dir(),
60-
DbNamePattern = <<DbName/binary, ".*">>,
61-
Pattern0 = filename:join([BaseDir, "shards", "*", DbNamePattern, "*"]),
62-
Pattern = binary_to_list(iolist_to_binary(Pattern0)),
63-
DirListStrs = filelib:wildcard(Pattern),
64-
DirList = [iolist_to_binary(DL) || DL <- DirListStrs],
65-
LocalShards = mem3:local_shards(DbName),
66-
ActiveDirs = lists:foldl(
67-
fun(LS, AccOuter) ->
68-
lists:foldl(
69-
fun(Sig, AccInner) ->
70-
DirName = filename:join([BaseDir, LS#shard.name, Sig]),
71-
[DirName | AccInner]
72-
end,
73-
AccOuter,
74-
ActiveSigs
75-
)
76-
end,
77-
[],
78-
LocalShards
79-
),
80-
81-
DeadDirs = DirList -- ActiveDirs,
82-
lists:foreach(
83-
fun(IdxDir) ->
84-
Sig = dreyfus_util:get_signature_from_idxdir(IdxDir),
85-
case Sig of
86-
undefined ->
87-
ok;
88-
_ ->
89-
DocId = dreyfus_util:get_local_purge_doc_id(Sig),
90-
LocalShards = mem3:local_shards(DbName),
91-
lists:foreach(
92-
fun(LS) ->
93-
ShardDbName = LS#shard.name,
94-
{ok, ShardDb} = couch_db:open_int(ShardDbName, []),
95-
case couch_db:open_doc(ShardDb, DocId, []) of
96-
{ok, LocalPurgeDoc} ->
97-
couch_db:update_doc(
98-
ShardDb,
99-
LocalPurgeDoc#doc{deleted = true},
100-
[?ADMIN_CTX]
101-
);
102-
{not_found, _} ->
103-
ok
104-
end,
105-
couch_db:close(ShardDb)
106-
end,
107-
LocalShards
108-
)
109-
end
110-
end,
111-
DeadDirs
112-
).
53+
recv(DbName, Reqs, Timeout) ->
54+
case erpc:receive_response(Reqs, Timeout, true) of
55+
{ok, _Lable, Reqs1} ->
56+
recv(DbName, Reqs1, Timeout);
57+
{Error, Label, Reqs1} ->
58+
ErrMsg = "~p : error cleaning dreyfus indexes db:~p req:~p error:~p",
59+
couch_log:error(ErrMsg, [?MODULE, DbName, Label, Error]),
60+
recv(DbName, Reqs1, Timeout);
61+
no_request ->
62+
ok
63+
end.

0 commit comments

Comments
 (0)