Support for recalc

Initial test included for running with recallc, and also transition from retain to recalc.

Moves all logic for startup fold into leveled_bookie - avoid the Inker requiring any direct knowledge about implementation of the Penciller.
This commit is contained in:
Martin Sumner 2020-03-15 22:14:42 +00:00
parent 1242dd4991
commit 694d2c39f8
7 changed files with 522 additions and 187 deletions

View file

@ -93,8 +93,6 @@
]). ]).
-export([empty_ledgercache/0, -export([empty_ledgercache/0,
loadqueue_ledgercache/1,
push_ledgercache/2,
snapshot_store/6, snapshot_store/6,
fetch_value/2, fetch_value/2,
journal_notfound/4]). journal_notfound/4]).
@ -105,6 +103,7 @@
-include_lib("eunit/include/eunit.hrl"). -include_lib("eunit/include/eunit.hrl").
-define(LOADING_PAUSE, 1000).
-define(CACHE_SIZE, 2500). -define(CACHE_SIZE, 2500).
-define(MIN_CACHE_SIZE, 100). -define(MIN_CACHE_SIZE, 100).
-define(MIN_PCL_CACHE_SIZE, 400). -define(MIN_PCL_CACHE_SIZE, 400).
@ -166,7 +165,7 @@
-record(state, {inker :: pid() | undefined, -record(state, {inker :: pid() | undefined,
penciller :: pid() | undefined, penciller :: pid() | undefined,
cache_size :: integer() | undefined, cache_size :: integer() | undefined,
ledger_cache = #ledger_cache{}, ledger_cache = #ledger_cache{} :: ledger_cache(),
is_snapshot :: boolean() | undefined, is_snapshot :: boolean() | undefined,
slow_offer = false :: boolean(), slow_offer = false :: boolean(),
@ -315,7 +314,7 @@
% Journal, to recalculate the index changes based on the current % Journal, to recalculate the index changes based on the current
% state of the Ledger and the object metadata. % state of the Ledger and the object metadata.
% %
% reload_strategy ptions are a list - to map from a tag to the % reload_strategy options are a list - to map from a tag to the
% strategy (recovr|retain|recalc). Defualt strategies are: % strategy (recovr|retain|recalc). Defualt strategies are:
% [{?RIAK_TAG, retain}, {?STD_TAG, retain}] % [{?RIAK_TAG, retain}, {?STD_TAG, retain}]
{max_pencillercachesize, pos_integer()|undefined} | {max_pencillercachesize, pos_integer()|undefined} |
@ -378,7 +377,16 @@
% true % true
]. ].
-type initial_loadfun() ::
fun((leveled_codec:journal_key(),
any(),
non_neg_integer(),
{non_neg_integer(), non_neg_integer(), ledger_cache()},
fun((any()) -> {binary(), non_neg_integer()})) ->
{loop|stop,
{non_neg_integer(), non_neg_integer(), ledger_cache()}}).
-export_type([initial_loadfun/0]).
%%%============================================================================ %%%============================================================================
%%% API %%% API
@ -1250,8 +1258,7 @@ handle_call({put, Bucket, Key, Object, IndexSpecs, Tag, TTL}, From, State)
SQN, SQN,
Object, Object,
ObjSize, ObjSize,
{IndexSpecs, TTL}, {IndexSpecs, TTL}),
State),
Cache0 = addto_ledgercache(Changes, State#state.ledger_cache), Cache0 = addto_ledgercache(Changes, State#state.ledger_cache),
{_SW2, Timings2} = update_timings(SW1, {put, mem}, Timings1), {_SW2, Timings2} = update_timings(SW1, {put, mem}, Timings1),
@ -1288,8 +1295,7 @@ handle_call({mput, ObjectSpecs, TTL}, From, State)
Changes = Changes =
preparefor_ledgercache(?INKT_MPUT, ?DUMMY, preparefor_ledgercache(?INKT_MPUT, ?DUMMY,
SQN, null, length(ObjectSpecs), SQN, null, length(ObjectSpecs),
{ObjectSpecs, TTL}, {ObjectSpecs, TTL}),
State),
Cache0 = addto_ledgercache(Changes, State#state.ledger_cache), Cache0 = addto_ledgercache(Changes, State#state.ledger_cache),
case State#state.slow_offer of case State#state.slow_offer of
true -> true ->
@ -1537,6 +1543,23 @@ code_change(_OldVsn, State, _Extra) ->
empty_ledgercache() -> empty_ledgercache() ->
#ledger_cache{mem = ets:new(empty, [ordered_set])}. #ledger_cache{mem = ets:new(empty, [ordered_set])}.
-spec push_to_penciller(pid(), ledger_cache()) -> ok.
%% @doc
%% The push to penciller must start as a tree to correctly de-duplicate
%% the list by order before becoming a de-duplicated list for loading
push_to_penciller(Penciller, LedgerCache) ->
push_to_penciller_loop(Penciller, loadqueue_ledgercache(LedgerCache)).
push_to_penciller_loop(Penciller, LedgerCache) ->
case push_ledgercache(Penciller, LedgerCache) of
returned ->
timer:sleep(?LOADING_PAUSE),
push_to_penciller_loop(Penciller, LedgerCache);
ok ->
ok
end.
-spec push_ledgercache(pid(), ledger_cache()) -> ok|returned. -spec push_ledgercache(pid(), ledger_cache()) -> ok|returned.
%% @doc %% @doc
%% Push the ledgercache to the Penciller - which should respond ok or %% Push the ledgercache to the Penciller - which should respond ok or
@ -1642,10 +1665,22 @@ startup(InkerOpts, PencillerOpts, State) ->
{ok, Penciller} = leveled_penciller:pcl_start(PencillerOpts), {ok, Penciller} = leveled_penciller:pcl_start(PencillerOpts),
LedgerSQN = leveled_penciller:pcl_getstartupsequencenumber(Penciller), LedgerSQN = leveled_penciller:pcl_getstartupsequencenumber(Penciller),
leveled_log:log("B0005", [LedgerSQN]), leveled_log:log("B0005", [LedgerSQN]),
ReloadStrategy = InkerOpts#inker_options.reload_strategy,
LoadFun = get_loadfun(ReloadStrategy, Penciller, State),
BatchFun =
fun(BatchAcc, _Acc) ->
push_to_penciller(Penciller, BatchAcc)
end,
InitAccFun =
fun(FN, CurrentMinSQN) ->
leveled_log:log("I0014", [FN, CurrentMinSQN]),
empty_ledgercache()
end,
ok = leveled_inker:ink_loadpcl(Inker, ok = leveled_inker:ink_loadpcl(Inker,
LedgerSQN + 1, LedgerSQN + 1,
get_loadfun(State), LoadFun,
Penciller), InitAccFun,
BatchFun),
ok = leveled_inker:ink_checksqn(Inker, LedgerSQN), ok = leveled_inker:ink_checksqn(Inker, LedgerSQN),
{Inker, Penciller}. {Inker, Penciller}.
@ -2161,30 +2196,26 @@ check_notfound(CheckFrequency, CheckFun) ->
-spec preparefor_ledgercache(leveled_codec:journal_key_tag()|null, -spec preparefor_ledgercache(leveled_codec:journal_key_tag()|null,
leveled_codec:ledger_key()|?DUMMY, leveled_codec:ledger_key()|?DUMMY,
integer(), any(), integer(), non_neg_integer(), any(), integer(),
leveled_codec:journal_keychanges(), leveled_codec:journal_keychanges())
book_state()) -> {leveled_codec:segment_hash(),
-> {integer()|no_lookup, non_neg_integer(),
integer(),
list(leveled_codec:ledger_kv())}. list(leveled_codec:ledger_kv())}.
%% @doc %% @doc
%% Prepare an object and its related key changes for addition to the Ledger %% Prepare an object and its related key changes for addition to the Ledger
%% via the Ledger Cache. %% via the Ledger Cache.
preparefor_ledgercache(?INKT_MPUT, preparefor_ledgercache(?INKT_MPUT,
?DUMMY, SQN, _O, _S, {ObjSpecs, TTL}, ?DUMMY, SQN, _O, _S, {ObjSpecs, TTL}) ->
_State) ->
ObjChanges = leveled_codec:obj_objectspecs(ObjSpecs, SQN, TTL), ObjChanges = leveled_codec:obj_objectspecs(ObjSpecs, SQN, TTL),
{no_lookup, SQN, ObjChanges}; {no_lookup, SQN, ObjChanges};
preparefor_ledgercache(?INKT_KEYD, preparefor_ledgercache(?INKT_KEYD,
LedgerKey, SQN, _Obj, _Size, {IdxSpecs, TTL}, LedgerKey, SQN, _Obj, _Size, {IdxSpecs, TTL}) ->
_State) ->
{Bucket, Key} = leveled_codec:from_ledgerkey(LedgerKey), {Bucket, Key} = leveled_codec:from_ledgerkey(LedgerKey),
KeyChanges = KeyChanges =
leveled_codec:idx_indexspecs(IdxSpecs, Bucket, Key, SQN, TTL), leveled_codec:idx_indexspecs(IdxSpecs, Bucket, Key, SQN, TTL),
{no_lookup, SQN, KeyChanges}; {no_lookup, SQN, KeyChanges};
preparefor_ledgercache(_InkTag, preparefor_ledgercache(_InkTag,
LedgerKey, SQN, Obj, Size, {IdxSpecs, TTL}, LedgerKey, SQN, Obj, Size, {IdxSpecs, TTL}) ->
_State) ->
{Bucket, Key, MetaValue, {KeyH, _ObjH}, _LastMods} = {Bucket, Key, MetaValue, {KeyH, _ObjH}, _LastMods} =
leveled_codec:generate_ledgerkv(LedgerKey, SQN, Obj, Size, TTL), leveled_codec:generate_ledgerkv(LedgerKey, SQN, Obj, Size, TTL),
KeyChanges = KeyChanges =
@ -2193,8 +2224,58 @@ preparefor_ledgercache(_InkTag,
{KeyH, SQN, KeyChanges}. {KeyH, SQN, KeyChanges}.
-spec addto_ledgercache({integer()|no_lookup, -spec recalcfor_ledgercache(leveled_codec:journal_key_tag()|null,
integer(), leveled_codec:ledger_key()|?DUMMY,
non_neg_integer(), any(), integer(),
leveled_codec:journal_keychanges(),
ledger_cache(),
pid())
-> {leveled_codec:segment_hash(),
non_neg_integer(),
list(leveled_codec:ledger_kv())}.
%% @doc
%% When loading from the journal to the ledger, may hit a key which has the
%% `recalc` strategy. Such a key needs to recalculate the key changes by
%% comparison with the current state of the ledger, assuming it is a full
%% journal entry (i.e. KeyDeltas which may be a result of previously running
%% with a retain strategy should be ignored).
recalcfor_ledgercache(InkTag,
_LedgerKey, SQN, _Obj, _Size, {_IdxSpecs, _TTL},
_LedgerCache,
_Penciller)
when InkTag == ?INKT_MPUT; InkTag == ?INKT_KEYD ->
{no_lookup, SQN, []};
recalcfor_ledgercache(_InkTag,
LK, SQN, Obj, Size, {_IgnoreJournalIdxSpecs, TTL},
LedgerCache,
Penciller) ->
{Bucket, Key, MetaValue, {KeyH, _ObjH}, _LastMods} =
leveled_codec:generate_ledgerkv(LK, SQN, Obj, Size, TTL),
OldObject =
case check_in_ledgercache(LK, KeyH, LedgerCache, loader) of
false ->
leveled_penciller:pcl_fetch(Penciller, LK, KeyH, true);
{value, KV} ->
KV
end,
OldMetadata =
case OldObject of
not_present ->
not_present;
{LK, LV} ->
leveled_codec:get_metadata(LV)
end,
UpdMetadata = leveled_codec:get_metadata(MetaValue),
IdxSpecs =
leveled_head:diff_indexspecs(element(1, LK), UpdMetadata, OldMetadata),
{KeyH,
SQN,
[{LK, MetaValue}]
++ leveled_codec:idx_indexspecs(IdxSpecs, Bucket, Key, SQN, TTL)}.
-spec addto_ledgercache({leveled_codec:segment_hash(),
non_neg_integer(),
list(leveled_codec:ledger_kv())}, list(leveled_codec:ledger_kv())},
ledger_cache()) ledger_cache())
-> ledger_cache(). -> ledger_cache().
@ -2230,6 +2311,32 @@ addto_ledgercache({H, SQN, KeyChanges}, Cache, loader) ->
max_sqn=max(SQN, Cache#ledger_cache.max_sqn)}. max_sqn=max(SQN, Cache#ledger_cache.max_sqn)}.
-spec check_in_ledgercache(leveled_codec:ledger_key(),
leveled_codec:segment_hash(),
ledger_cache(),
loader) ->
false | {value, leveled_codec:ledger_kv()}.
%% @doc
%% Check the ledger cache for a Key, when the ledger cache is in loader mode
%% and so is populating a queue not an ETS table
check_in_ledgercache(PK, Hash, Cache, loader) ->
case leveled_pmem:check_index(Hash, Cache#ledger_cache.index) of
[] ->
false;
_ ->
search(fun({K,_V}) -> K == PK end,
lists:reverse(Cache#ledger_cache.load_queue))
end.
-spec search(fun((any()) -> boolean()), list()) -> {value, any()}|false.
search(Pred, [Hd|Tail]) ->
case Pred(Hd) of
true -> {value, Hd};
false -> search(Pred, Tail)
end;
search(Pred, []) when is_function(Pred, 1) ->
false.
-spec maybepush_ledgercache(integer(), ledger_cache(), pid()) -spec maybepush_ledgercache(integer(), ledger_cache(), pid())
-> {ok|returned, ledger_cache()}. -> {ok|returned, ledger_cache()}.
%% @doc %% @doc
@ -2276,44 +2383,47 @@ maybe_withjitter(_CacheSize, _MaxCacheSize) ->
false. false.
-spec get_loadfun(book_state()) -> fun(). -spec get_loadfun(leveled_codec:compaction_strategy(), pid(), book_state())
-> initial_loadfun().
%% @doc %% @doc
%% The LoadFun will be used by the Inker when walking across the Journal to %% The LoadFun will be used by the Inker when walking across the Journal to
%% load the Penciller at startup %% load the Penciller at startup.
get_loadfun(State) -> get_loadfun(ReloadStrat, Penciller, _State) ->
PrepareFun =
fun(Tag, PK, SQN, Obj, VS, IdxSpecs) ->
preparefor_ledgercache(Tag, PK, SQN, Obj, VS, IdxSpecs, State)
end,
LoadFun =
fun(KeyInJournal, ValueInJournal, _Pos, Acc0, ExtractFun) -> fun(KeyInJournal, ValueInJournal, _Pos, Acc0, ExtractFun) ->
{MinSQN, MaxSQN, OutputTree} = Acc0, {MinSQN, MaxSQN, LedgerCache} = Acc0,
{SQN, InkTag, PK} = KeyInJournal, {SQN, InkTag, PK} = KeyInJournal,
% VBin may already be a term
{VBin, VSize} = ExtractFun(ValueInJournal),
{Obj, IdxSpecs} = leveled_codec:split_inkvalue(VBin),
case SQN of case SQN of
SQN when SQN < MinSQN -> SQN when SQN < MinSQN ->
{loop, Acc0}; {loop, Acc0};
SQN when SQN < MaxSQN ->
Chngs = PrepareFun(InkTag, PK, SQN, Obj, VSize, IdxSpecs),
{loop,
{MinSQN,
MaxSQN,
addto_ledgercache(Chngs, OutputTree, loader)}};
MaxSQN ->
leveled_log:log("B0006", [SQN]),
Chngs = PrepareFun(InkTag, PK, SQN, Obj, VSize, IdxSpecs),
{stop,
{MinSQN,
MaxSQN,
addto_ledgercache(Chngs, OutputTree, loader)}};
SQN when SQN > MaxSQN -> SQN when SQN > MaxSQN ->
leveled_log:log("B0007", [MaxSQN, SQN]), leveled_log:log("B0007", [MaxSQN, SQN]),
{stop, Acc0} {stop, Acc0};
end _ ->
{VBin, ValSize} = ExtractFun(ValueInJournal),
% VBin may already be a term
{Obj, IdxSpecs} = leveled_codec:split_inkvalue(VBin),
Chngs =
case leveled_codec:get_tagstrategy(PK, ReloadStrat) of
recalc ->
recalcfor_ledgercache(InkTag, PK, SQN,
Obj, ValSize, IdxSpecs,
LedgerCache,
Penciller);
_ ->
preparefor_ledgercache(InkTag, PK, SQN,
Obj, ValSize, IdxSpecs)
end, end,
LoadFun. case SQN of
MaxSQN ->
leveled_log:log("B0006", [SQN]),
LC0 = addto_ledgercache(Chngs, LedgerCache, loader),
{stop, {MinSQN, MaxSQN, LC0}};
_ ->
LC0 = addto_ledgercache(Chngs, LedgerCache, loader),
{loop, {MinSQN, MaxSQN, LC0}}
end
end
end.
delete_path(DirPath) -> delete_path(DirPath) ->

View file

@ -49,7 +49,8 @@
segment_hash/1, segment_hash/1,
to_lookup/1, to_lookup/1,
next_key/1, next_key/1,
return_proxy/4]). return_proxy/4,
get_metadata/1]).
-define(LMD_FORMAT, "~4..0w~2..0w~2..0w~2..0w~2..0w"). -define(LMD_FORMAT, "~4..0w~2..0w~2..0w~2..0w~2..0w").
-define(NRT_IDX, "$aae."). -define(NRT_IDX, "$aae.").
@ -243,6 +244,10 @@ strip_to_indexdetails({_, V}) when tuple_size(V) > 4 ->
striphead_to_v1details(V) -> striphead_to_v1details(V) ->
{element(1, V), element(2, V), element(3, V), element(4, V)}. {element(1, V), element(2, V), element(3, V), element(4, V)}.
-spec get_metadata(ledger_value()) -> metadata().
get_metadata(LV) ->
element(4, LV).
-spec key_dominates(ledger_kv(), ledger_kv()) -> -spec key_dominates(ledger_kv(), ledger_kv()) ->
left_hand_first|right_hand_first|left_hand_dominant|right_hand_dominant. left_hand_first|right_hand_first|left_hand_dominant|right_hand_dominant.
%% @doc %% @doc
@ -358,7 +363,7 @@ endkey_passed(EndKey, CheckingKey) ->
-spec inker_reload_strategy(compaction_strategy()) -> compaction_strategy(). -spec inker_reload_strategy(compaction_strategy()) -> compaction_strategy().
%% @doc %% @doc
%% Take the default startegy for compaction, and override the approach for any %% Take the default strategy for compaction, and override the approach for any
%% tags passed in %% tags passed in
inker_reload_strategy(AltList) -> inker_reload_strategy(AltList) ->
ReloadStrategy0 = ReloadStrategy0 =
@ -371,11 +376,13 @@ inker_reload_strategy(AltList) ->
AltList). AltList).
-spec get_tagstrategy(ledger_key(), compaction_strategy()) -spec get_tagstrategy(ledger_key()|tag()|dummy, compaction_strategy())
-> skip|retain|recalc. -> skip|retain|recalc.
%% @doc %% @doc
%% Work out the compaction strategy for the key %% Work out the compaction strategy for the key
get_tagstrategy({Tag, _, _, _}, Strategy) -> get_tagstrategy({Tag, _, _, _}, Strategy) ->
get_tagstrategy(Tag, Strategy);
get_tagstrategy(Tag, Strategy) ->
case lists:keyfind(Tag, 1, Strategy) of case lists:keyfind(Tag, 1, Strategy) of
{Tag, TagStrat} -> {Tag, TagStrat} ->
TagStrat; TagStrat;

View file

@ -22,7 +22,8 @@
-export([key_to_canonicalbinary/1, -export([key_to_canonicalbinary/1,
build_head/2, build_head/2,
extract_metadata/3 extract_metadata/3,
diff_indexspecs/3
]). ]).
-export([get_size/2, -export([get_size/2,
@ -71,12 +72,15 @@
-type object_metadata() :: riak_metadata()|std_metadata()|head_metadata(). -type object_metadata() :: riak_metadata()|std_metadata()|head_metadata().
-type appdefinable_function() :: -type appdefinable_function() ::
key_to_canonicalbinary | build_head | extract_metadata. key_to_canonicalbinary | build_head | extract_metadata | diff_indexspecs.
% Functions for which default behaviour can be over-written for the % Functions for which default behaviour can be over-written for the
% application's own tags % application's own tags
-type appdefinable_function_tuple() :: -type appdefinable_function_tuple() ::
{appdefinable_function(), fun()}. {appdefinable_function(), fun()}.
-type index_op() :: add | remove.
-type index_value() :: integer() | binary().
-type head() :: -type head() ::
binary()|tuple(). binary()|tuple().
% TODO: % TODO:
@ -174,6 +178,41 @@ default_extract_metadata(_Tag, SizeAsStoredInJournal, Obj) ->
{{standard_hash(Obj), SizeAsStoredInJournal, undefined}, []}. {{standard_hash(Obj), SizeAsStoredInJournal, undefined}, []}.
-spec diff_indexspecs(object_tag(),
object_metadata(),
object_metadata()|not_present)
-> leveled_codec:index_specs().
%% @doc
%% Take an object metadata part from within the journal, and an object metadata
%% part from the ledger (which should have a lower SQN), and generate index
%% specs by determining the difference between the index specs on the object
%% to be loaded and that on object already stored.
%%
%% This is only relevant where the journal compaction strategy of `recalc` is
%% used, the Keychanges will be used when `retain` is the compaction strategy
diff_indexspecs(?RIAK_TAG, UpdatedMetadata, OldMetadata) ->
UpdIndexes =
get_indexes_from_siblingmetabin(element(1, UpdatedMetadata), []),
OldIndexes =
case OldMetadata of
not_present ->
[];
_ ->
get_indexes_from_siblingmetabin(element(1, OldMetadata), [])
end,
diff_index_data(OldIndexes, UpdIndexes);
diff_indexspecs(?STD_TAG, UpdatedMetadata, CurrentMetadata) ->
default_diff_indexspecs(?STD_TAG, UpdatedMetadata, CurrentMetadata);
diff_indexspecs(Tag, UpdatedMetadata, CurrentMetadata) ->
OverrideFun =
get_appdefined_function(diff_indexspecs,
fun default_diff_indexspecs/3,
3),
OverrideFun(Tag, UpdatedMetadata, CurrentMetadata).
default_diff_indexspecs(_Tag, _UpdatedMetadata, _CurrentMetadata) ->
[].
%%%============================================================================ %%%============================================================================
%%% Standard External Functions %%% Standard External Functions
%%%============================================================================ %%%============================================================================
@ -190,7 +229,7 @@ defined_objecttags() ->
leveled_codec:compaction_method()}. leveled_codec:compaction_method()}.
%% @doc %% @doc
%% State the compaction_method to be used when reloading the Ledger from the %% State the compaction_method to be used when reloading the Ledger from the
%% journal for each object tag. Note, no compaction startegy required for %% journal for each object tag. Note, no compaction strategy required for
%% head_only tag %% head_only tag
default_reload_strategy(Tag) -> default_reload_strategy(Tag) ->
{Tag, retain}. {Tag, retain}.
@ -317,3 +356,120 @@ get_metadata_from_siblings(<<ValLen:32/integer, Rest0/binary>>,
MetaLen:32/integer, MetaLen:32/integer,
MetaBin:MetaLen/binary>>, MetaBin:MetaLen/binary>>,
[LastMod|LastMods]). [LastMod|LastMods]).
get_indexes_from_siblingmetabin(<<0:32/integer,
MetaLen:32/integer,
MetaBin:MetaLen/binary,
RestBin/binary>>,
Indexes) ->
UpdIndexes = lists:umerge(get_indexes_frommetabin(MetaBin), Indexes),
get_indexes_from_siblingmetabin(RestBin, UpdIndexes);
get_indexes_from_siblingmetabin(<<SibCount:32/integer, RestBin/binary>>,
Indexes) when SibCount > 0 ->
get_indexes_from_siblingmetabin(RestBin, Indexes);
get_indexes_from_siblingmetabin(_, Indexes) ->
Indexes.
%% @doc
%% Parse the metabinary for an individual sibling and return a list of index
%% entries.
get_indexes_frommetabin(<<_LMD1:32/integer, _LMD2:32/integer, _LMD3:32/integer,
VTagLen:8/integer, _VTag:VTagLen/binary,
Deleted:1/binary-unit:8,
MetaRestBin/binary>>) when Deleted /= <<1>> ->
lists:usort(indexes_of_metabinary(MetaRestBin));
get_indexes_frommetabin(_) ->
[].
indexes_of_metabinary(<<>>) ->
[];
indexes_of_metabinary(<<KeyLen:32/integer, KeyBin:KeyLen/binary,
ValueLen:32/integer, ValueBin:ValueLen/binary,
Rest/binary>>) ->
Key = decode_maybe_binary(KeyBin),
case Key of
<<"index">> ->
Value = decode_maybe_binary(ValueBin),
Value;
_ ->
indexes_of_metabinary(Rest)
end.
decode_maybe_binary(<<1, Bin/binary>>) ->
Bin;
decode_maybe_binary(<<0, Bin/binary>>) ->
binary_to_term(Bin);
decode_maybe_binary(<<_Other:8, Bin/binary>>) ->
Bin.
-spec diff_index_data([{binary(), index_value()}],
[{binary(), index_value()}]) ->
[{index_op(), binary(), index_value()}].
diff_index_data(OldIndexes, AllIndexes) ->
OldIndexSet = ordsets:from_list(OldIndexes),
AllIndexSet = ordsets:from_list(AllIndexes),
diff_specs_core(AllIndexSet, OldIndexSet).
diff_specs_core(AllIndexSet, OldIndexSet) ->
NewIndexSet = ordsets:subtract(AllIndexSet, OldIndexSet),
RemoveIndexSet =
ordsets:subtract(OldIndexSet, AllIndexSet),
NewIndexSpecs =
assemble_index_specs(ordsets:subtract(NewIndexSet, OldIndexSet),
add),
RemoveIndexSpecs =
assemble_index_specs(RemoveIndexSet,
remove),
NewIndexSpecs ++ RemoveIndexSpecs.
%% @doc Assemble a list of index specs in the
%% form of triplets of the form
%% {IndexOperation, IndexField, IndexValue}.
-spec assemble_index_specs([{binary(), binary()}], index_op()) ->
[{index_op(), binary(), binary()}].
assemble_index_specs(Indexes, IndexOp) ->
[{IndexOp, Index, Value} || {Index, Value} <- Indexes].
%%%============================================================================
%%% Test
%%%============================================================================
-ifdef(TEST).
index_extract_test() ->
SibMetaBin = <<0,0,0,1,0,0,0,0,0,0,0,221,0,0,6,48,0,4,130,247,0,1,250,134,
1,101,0,0,0,0,4,1,77,68,75,0,0,0,44,0,131,107,0,39,77,68,
86,101,49,55,52,55,48,50,55,45,54,50,99,49,45,52,48,57,55,
45,97,53,102,50,45,53,54,98,51,98,97,57,57,99,55,56,50,0,0,
0,6,1,105,110,100,101,120,0,0,0,79,0,131,108,0,0,0,2,104,2,
107,0,8,105,100,120,49,95,98,105,110,107,0,20,50,49,53,50,
49,49,48,55,50,51,49,55,51,48,83,111,112,104,105,97,104,2,
107,0,8,105,100,120,49,95,98,105,110,107,0,19,50,49,56,50,
48,53,49,48,49,51,48,49,52,54,65,118,101,114,121,106,0,0,0,
5,1,77,68,75,50,0,0,0,44,0,131,107,0,39,77,68,86,101,49,55,
52,55,48,50,55,45,54,50,99,49,45,52,48,57,55,45,97,53,102,
50,45,53,54,98,51,98,97,57,57,99,55,56,50>>,
Indexes = get_indexes_from_siblingmetabin(SibMetaBin, []),
ExpIndexes = [{"idx1_bin","21521107231730Sophia"},
{"idx1_bin","21820510130146Avery"}],
?assertMatch(ExpIndexes, Indexes).
diff_index_test() ->
UpdIndexes =
[{<<"idx1_bin">>,<<"20840930001702Zoe">>},
{<<"idx1_bin">>,<<"20931011172606Emily">>}],
OldIndexes =
[{<<"idx1_bin">>,<<"20231126131808Madison">>},
{<<"idx1_bin">>,<<"20931011172606Emily">>}],
IdxSpecs = diff_index_data(OldIndexes, UpdIndexes),
?assertMatch([{add, <<"idx1_bin">>, <<"20840930001702Zoe">>},
{remove, <<"idx1_bin">>,<<"20231126131808Madison">>}], IdxSpecs).
-endif.

View file

@ -145,6 +145,15 @@
% released from a compaction run of a single file to make it a run % released from a compaction run of a single file to make it a run
% worthwhile of compaction (released space is 100.0 - target e.g. 70.0 % worthwhile of compaction (released space is 100.0 - target e.g. 70.0
% means that 30.0% should be released) % means that 30.0% should be released)
-type key_size() ::
{{non_neg_integer(),
leveled_codec:journal_key_tag(),
leveled_codec:ledger_key()}, non_neg_integer()}.
-type corrupted_test_key_size() ::
{{non_neg_integer(),
leveled_codec:journal_key_tag(),
leveled_codec:ledger_key(),
null}, non_neg_integer()}.
%%%============================================================================ %%%============================================================================
%%% API %%% API
@ -315,7 +324,8 @@ handle_cast({score_filelist, [Entry|Tail]}, State) ->
ScoringState#scoring_state.filter_server, ScoringState#scoring_state.filter_server,
ScoringState#scoring_state.max_sqn, ScoringState#scoring_state.max_sqn,
?SAMPLE_SIZE, ?SAMPLE_SIZE,
?BATCH_SIZE), ?BATCH_SIZE,
State#state.reload_strategy),
Candidate = Candidate =
#candidate{low_sqn = LowSQN, #candidate{low_sqn = LowSQN,
filename = FN, filename = FN,
@ -493,7 +503,10 @@ schedule_compaction(CompactionHours, RunsPerDay, CurrentTS) ->
%%% Internal functions %%% Internal functions
%%%============================================================================ %%%============================================================================
-spec check_single_file(pid(), fun(), any(), non_neg_integer(),
non_neg_integer(), non_neg_integer(),
leveled_codec:compaction_strategy()) ->
float().
%% @doc %% @doc
%% Get a score for a single CDB file in the journal. This will pull out a bunch %% Get a score for a single CDB file in the journal. This will pull out a bunch
%% of keys and sizes at random in an efficient way (by scanning the hashtable %% of keys and sizes at random in an efficient way (by scanning the hashtable
@ -505,13 +518,19 @@ schedule_compaction(CompactionHours, RunsPerDay, CurrentTS) ->
%% %%
%% The score is based on a random sample - so will not be consistent between %% The score is based on a random sample - so will not be consistent between
%% calls. %% calls.
check_single_file(CDB, FilterFun, FilterServer, MaxSQN, SampleSize, BatchSize) -> check_single_file(CDB, FilterFun, FilterServer, MaxSQN,
SampleSize, BatchSize,
ReloadStrategy) ->
FN = leveled_cdb:cdb_filename(CDB), FN = leveled_cdb:cdb_filename(CDB),
SW = os:timestamp(), SW = os:timestamp(),
PositionList = leveled_cdb:cdb_getpositions(CDB, SampleSize), PositionList = leveled_cdb:cdb_getpositions(CDB, SampleSize),
KeySizeList = fetch_inbatches(PositionList, BatchSize, CDB, []), KeySizeList = fetch_inbatches(PositionList, BatchSize, CDB, []),
Score = Score =
size_comparison_score(KeySizeList, FilterFun, FilterServer, MaxSQN), size_comparison_score(KeySizeList,
FilterFun,
FilterServer,
MaxSQN,
ReloadStrategy),
safely_log_filescore(PositionList, FN, Score, SW), safely_log_filescore(PositionList, FN, Score, SW),
Score. Score.
@ -523,7 +542,15 @@ safely_log_filescore(PositionList, FN, Score, SW) ->
div length(PositionList), div length(PositionList),
leveled_log:log_timer("IC004", [Score, AvgJump, FN], SW). leveled_log:log_timer("IC004", [Score, AvgJump, FN], SW).
size_comparison_score(KeySizeList, FilterFun, FilterServer, MaxSQN) -> -spec size_comparison_score(list(key_size() | corrupted_test_key_size()),
fun(),
any(),
non_neg_integer(),
leveled_codec:compaction_strategy()) ->
float().
size_comparison_score(KeySizeList,
FilterFun, FilterServer, MaxSQN,
RS) ->
FoldFunForSizeCompare = FoldFunForSizeCompare =
fun(KS, {ActSize, RplSize}) -> fun(KS, {ActSize, RplSize}) ->
case KS of case KS of
@ -532,7 +559,18 @@ size_comparison_score(KeySizeList, FilterFun, FilterServer, MaxSQN) ->
leveled_codec:is_full_journalentry({SQN, Type, PK}), leveled_codec:is_full_journalentry({SQN, Type, PK}),
case IsJournalEntry of case IsJournalEntry of
false -> false ->
TS = leveled_codec:get_tagstrategy(PK, RS),
% If the strategy is to retain key deltas, then
% scoring must reflect that. Key deltas are
% possible even if strategy does not allow as
% there is support for changing strategy from
% retain to recalc
case TS of
retain ->
{ActSize + Size - ?CRC_SIZE, RplSize}; {ActSize + Size - ?CRC_SIZE, RplSize};
_ ->
{ActSize, RplSize + Size - ?CRC_SIZE}
end;
true -> true ->
Check = FilterFun(FilterServer, PK, SQN), Check = FilterFun(FilterServer, PK, SQN),
case {Check, SQN > MaxSQN} of case {Check, SQN > MaxSQN} of
@ -567,7 +605,8 @@ fetch_inbatches([], _BatchSize, CDB, CheckedList) ->
ok = leveled_cdb:cdb_clerkcomplete(CDB), ok = leveled_cdb:cdb_clerkcomplete(CDB),
CheckedList; CheckedList;
fetch_inbatches(PositionList, BatchSize, CDB, CheckedList) -> fetch_inbatches(PositionList, BatchSize, CDB, CheckedList) ->
{Batch, Tail} = if {Batch, Tail} =
if
length(PositionList) >= BatchSize -> length(PositionList) >= BatchSize ->
lists:split(BatchSize, PositionList); lists:split(BatchSize, PositionList);
true -> true ->
@ -998,6 +1037,7 @@ fetch_testcdb(RP) ->
check_single_file_test() -> check_single_file_test() ->
RP = "test/test_area/", RP = "test/test_area/",
RS = leveled_codec:inker_reload_strategy([]),
ok = filelib:ensure_dir(leveled_inker:filepath(RP, journal_dir)), ok = filelib:ensure_dir(leveled_inker:filepath(RP, journal_dir)),
{ok, CDB} = fetch_testcdb(RP), {ok, CDB} = fetch_testcdb(RP),
LedgerSrv1 = [{8, {o, "Bucket", "Key1", null}}, LedgerSrv1 = [{8, {o, "Bucket", "Key1", null}},
@ -1010,14 +1050,14 @@ check_single_file_test() ->
_ -> _ ->
replaced replaced
end end, end end,
Score1 = check_single_file(CDB, LedgerFun1, LedgerSrv1, 9, 8, 4), Score1 = check_single_file(CDB, LedgerFun1, LedgerSrv1, 9, 8, 4, RS),
?assertMatch(37.5, Score1), ?assertMatch(37.5, Score1),
LedgerFun2 = fun(_Srv, _Key, _ObjSQN) -> current end, LedgerFun2 = fun(_Srv, _Key, _ObjSQN) -> current end,
Score2 = check_single_file(CDB, LedgerFun2, LedgerSrv1, 9, 8, 4), Score2 = check_single_file(CDB, LedgerFun2, LedgerSrv1, 9, 8, 4, RS),
?assertMatch(100.0, Score2), ?assertMatch(100.0, Score2),
Score3 = check_single_file(CDB, LedgerFun1, LedgerSrv1, 9, 8, 3), Score3 = check_single_file(CDB, LedgerFun1, LedgerSrv1, 9, 8, 3, RS),
?assertMatch(37.5, Score3), ?assertMatch(37.5, Score3),
Score4 = check_single_file(CDB, LedgerFun1, LedgerSrv1, 4, 8, 4), Score4 = check_single_file(CDB, LedgerFun1, LedgerSrv1, 4, 8, 4, RS),
?assertMatch(75.0, Score4), ?assertMatch(75.0, Score4),
ok = leveled_cdb:cdb_deletepending(CDB), ok = leveled_cdb:cdb_deletepending(CDB),
ok = leveled_cdb:cdb_destroy(CDB). ok = leveled_cdb:cdb_destroy(CDB).
@ -1132,6 +1172,7 @@ compact_empty_file_test() ->
RP = "test/test_area/", RP = "test/test_area/",
ok = filelib:ensure_dir(leveled_inker:filepath(RP, journal_dir)), ok = filelib:ensure_dir(leveled_inker:filepath(RP, journal_dir)),
FN1 = leveled_inker:filepath(RP, 1, new_journal), FN1 = leveled_inker:filepath(RP, 1, new_journal),
RS = leveled_codec:inker_reload_strategy([]),
CDBopts = #cdb_options{binary_mode=true}, CDBopts = #cdb_options{binary_mode=true},
{ok, CDB1} = leveled_cdb:cdb_open_writer(FN1, CDBopts), {ok, CDB1} = leveled_cdb:cdb_open_writer(FN1, CDBopts),
{ok, FN2} = leveled_cdb:cdb_complete(CDB1), {ok, FN2} = leveled_cdb:cdb_complete(CDB1),
@ -1140,7 +1181,7 @@ compact_empty_file_test() ->
{2, {o, "Bucket", "Key2", null}}, {2, {o, "Bucket", "Key2", null}},
{3, {o, "Bucket", "Key3", null}}], {3, {o, "Bucket", "Key3", null}}],
LedgerFun1 = fun(_Srv, _Key, _ObjSQN) -> replaced end, LedgerFun1 = fun(_Srv, _Key, _ObjSQN) -> replaced end,
Score1 = check_single_file(CDB2, LedgerFun1, LedgerSrv1, 9, 8, 4), Score1 = check_single_file(CDB2, LedgerFun1, LedgerSrv1, 9, 8, 4, RS),
?assertMatch(0.0, Score1), ?assertMatch(0.0, Score1),
ok = leveled_cdb:cdb_deletepending(CDB2), ok = leveled_cdb:cdb_deletepending(CDB2),
ok = leveled_cdb:cdb_destroy(CDB2). ok = leveled_cdb:cdb_destroy(CDB2).
@ -1207,15 +1248,22 @@ compact_singlefile_totwosmallfiles_testto() ->
size_score_test() -> size_score_test() ->
KeySizeList = KeySizeList =
[{{1, ?INKT_STND, "Key1"}, 104}, [{{1, ?INKT_STND, {?STD_TAG, <<"B">>, <<"Key1">>, null}}, 104},
{{2, ?INKT_STND, "Key2"}, 124}, {{2, ?INKT_STND, {?STD_TAG, <<"B">>, <<"Key2">>, null}}, 124},
{{3, ?INKT_STND, "Key3"}, 144}, {{3, ?INKT_STND, {?STD_TAG, <<"B">>, <<"Key3">>, null}}, 144},
{{4, ?INKT_STND, "Key4"}, 154}, {{4, ?INKT_STND, {?STD_TAG, <<"B">>, <<"Key4">>, null}}, 154},
{{5, ?INKT_STND, "Key5", "Subk1"}, 164}, {{5,
{{6, ?INKT_STND, "Key6"}, 174}, ?INKT_STND,
{{7, ?INKT_STND, "Key7"}, 184}], {?STD_TAG, <<"B">>, <<"Key5">>, <<"Subk1">>}, null},
164},
{{6, ?INKT_STND, {?STD_TAG, <<"B">>, <<"Key6">>, null}}, 174},
{{7, ?INKT_STND, {?STD_TAG, <<"B">>, <<"Key7">>, null}}, 184}],
MaxSQN = 6, MaxSQN = 6,
CurrentList = ["Key1", "Key4", "Key5", "Key6"], CurrentList =
[{?STD_TAG, <<"B">>, <<"Key1">>, null},
{?STD_TAG, <<"B">>, <<"Key4">>, null},
{?STD_TAG, <<"B">>, <<"Key5">>, <<"Subk1">>},
{?STD_TAG, <<"B">>, <<"Key6">>, null}],
FilterFun = FilterFun =
fun(L, K, _SQN) -> fun(L, K, _SQN) ->
case lists:member(K, L) of case lists:member(K, L) of
@ -1223,7 +1271,13 @@ size_score_test() ->
false -> replaced false -> replaced
end end
end, end,
Score = size_comparison_score(KeySizeList, FilterFun, CurrentList, MaxSQN), Score =
size_comparison_score(KeySizeList,
FilterFun,
CurrentList,
MaxSQN,
leveled_codec:inker_reload_strategy([])),
io:format("Score ~w", [Score]),
?assertMatch(true, Score > 69.0), ?assertMatch(true, Score > 69.0),
?assertMatch(true, Score < 70.0). ?assertMatch(true, Score < 70.0).

View file

@ -101,7 +101,7 @@
ink_fetch/3, ink_fetch/3,
ink_keycheck/3, ink_keycheck/3,
ink_fold/4, ink_fold/4,
ink_loadpcl/4, ink_loadpcl/5,
ink_registersnapshot/2, ink_registersnapshot/2,
ink_confirmdelete/2, ink_confirmdelete/2,
ink_compactjournal/3, ink_compactjournal/3,
@ -133,7 +133,6 @@
-define(WASTE_FP, "waste"). -define(WASTE_FP, "waste").
-define(JOURNAL_FILEX, "cdb"). -define(JOURNAL_FILEX, "cdb").
-define(PENDING_FILEX, "pnd"). -define(PENDING_FILEX, "pnd").
-define(LOADING_PAUSE, 1000).
-define(LOADING_BATCH, 1000). -define(LOADING_BATCH, 1000).
-define(TEST_KC, {[], infinity}). -define(TEST_KC, {[], infinity}).
@ -321,7 +320,11 @@ ink_fold(Pid, MinSQN, FoldFuns, Acc) ->
{fold, MinSQN, FoldFuns, Acc, by_runner}, {fold, MinSQN, FoldFuns, Acc, by_runner},
infinity). infinity).
-spec ink_loadpcl(pid(), integer(), fun(), pid()) -> ok. -spec ink_loadpcl(pid(),
integer(),
leveled_bookie:initial_loadfun(),
fun((string(), non_neg_integer()) -> any()),
fun((any(), any()) -> ok)) -> ok.
%% %%
%% Function to prompt load of the Ledger at startup. The Penciller should %% Function to prompt load of the Ledger at startup. The Penciller should
%% have determined the lowest SQN not present in the Ledger, and the inker %% have determined the lowest SQN not present in the Ledger, and the inker
@ -330,20 +333,11 @@ ink_fold(Pid, MinSQN, FoldFuns, Acc) ->
%% %%
%% The load fun should be a five arity function like: %% The load fun should be a five arity function like:
%% load_fun(KeyInJournal, ValueInJournal, _Position, Acc0, ExtractFun) %% load_fun(KeyInJournal, ValueInJournal, _Position, Acc0, ExtractFun)
ink_loadpcl(Pid, MinSQN, FilterFun, Penciller) -> ink_loadpcl(Pid, MinSQN, LoadFun, InitAccFun, BatchFun) ->
BatchFun =
fun(BatchAcc, _Acc) ->
push_to_penciller(Penciller, BatchAcc)
end,
InitAccFun =
fun(FN, CurrentMinSQN) ->
leveled_log:log("I0014", [FN, CurrentMinSQN]),
leveled_bookie:empty_ledgercache()
end,
gen_server:call(Pid, gen_server:call(Pid,
{fold, {fold,
MinSQN, MinSQN,
{FilterFun, InitAccFun, BatchFun}, {LoadFun, InitAccFun, BatchFun},
ok, ok,
as_ink}, as_ink},
infinity). infinity).
@ -1197,22 +1191,6 @@ foldfile_between_sequence(MinSQN, MaxSQN, FoldFuns,
end. end.
push_to_penciller(Penciller, LedgerCache) ->
% The push to penciller must start as a tree to correctly de-duplicate
% the list by order before becoming a de-duplicated list for loading
LC0 = leveled_bookie:loadqueue_ledgercache(LedgerCache),
push_to_penciller_loop(Penciller, LC0).
push_to_penciller_loop(Penciller, LedgerCache) ->
case leveled_bookie:push_ledgercache(Penciller, LedgerCache) of
returned ->
timer:sleep(?LOADING_PAUSE),
push_to_penciller_loop(Penciller, LedgerCache);
ok ->
ok
end.
sequencenumbers_fromfilenames(Filenames, Regex, IntName) -> sequencenumbers_fromfilenames(Filenames, Regex, IntName) ->
lists:foldl(fun(FN, Acc) -> lists:foldl(fun(FN, Acc) ->
case re:run(FN, case re:run(FN,

View file

@ -7,6 +7,8 @@
hot_backup_simple/1, hot_backup_simple/1,
hot_backup_changes/1, hot_backup_changes/1,
retain_strategy/1, retain_strategy/1,
recalc_strategy/1,
recalc_transition_strategy/1,
recovr_strategy/1, recovr_strategy/1,
aae_missingjournal/1, aae_missingjournal/1,
aae_bustedjournal/1, aae_bustedjournal/1,
@ -21,6 +23,8 @@ all() -> [
hot_backup_simple, hot_backup_simple,
hot_backup_changes, hot_backup_changes,
retain_strategy, retain_strategy,
recalc_strategy,
recalc_transition_strategy,
recovr_strategy, recovr_strategy,
aae_missingjournal, aae_missingjournal,
aae_bustedjournal, aae_bustedjournal,
@ -233,85 +237,97 @@ hot_backup_changes(_Config) ->
testutil:reset_filestructure(). testutil:reset_filestructure().
retain_strategy(_Config) -> retain_strategy(_Config) ->
rotate_wipe_compact(retain, retain).
recalc_strategy(_Config) ->
rotate_wipe_compact(recalc, recalc).
recalc_transition_strategy(_Config) ->
rotate_wipe_compact(retain, recalc).
rotate_wipe_compact(Strategy1, Strategy2) ->
RootPath = testutil:reset_filestructure(), RootPath = testutil:reset_filestructure(),
BookOpts = [{root_path, RootPath}, BookOpts = [{root_path, RootPath},
{cache_size, 1000}, {cache_size, 1000},
{max_journalobjectcount, 5000}, {max_journalobjectcount, 5000},
{sync_strategy, testutil:sync_strategy()}, {sync_strategy, testutil:sync_strategy()},
{reload_strategy, [{?RIAK_TAG, retain}]}], {reload_strategy, [{?RIAK_TAG, Strategy1}]}],
BookOptsAlt = [{root_path, RootPath}, BookOptsAlt = [{root_path, RootPath},
{cache_size, 1000}, {cache_size, 1000},
{max_journalobjectcount, 2000}, {max_journalobjectcount, 2000},
{sync_strategy, testutil:sync_strategy()}, {sync_strategy, testutil:sync_strategy()},
{reload_strategy, [{?RIAK_TAG, retain}]}, {reload_strategy, [{?RIAK_TAG, Strategy2}]},
{max_run_length, 8}], {max_run_length, 8}],
{ok, Spcl3, LastV3} = rotating_object_check(BookOpts, "Bucket3", 800), {ok, Spcl3, LastV3} = rotating_object_check(BookOpts, "Bucket3", 400),
ok = restart_from_blankledger(BookOpts, [{"Bucket3", Spcl3, LastV3}]), ok = restart_from_blankledger(BookOpts, [{"Bucket3", Spcl3, LastV3}]),
{ok, Spcl4, LastV4} = rotating_object_check(BookOpts, "Bucket4", 1600), {ok, Spcl4, LastV4} = rotating_object_check(BookOpts, "Bucket4", 800),
ok = restart_from_blankledger(BookOpts, [{"Bucket3", Spcl3, LastV3}, ok = restart_from_blankledger(BookOpts, [{"Bucket3", Spcl3, LastV3},
{"Bucket4", Spcl4, LastV4}]), {"Bucket4", Spcl4, LastV4}]),
{ok, Spcl5, LastV5} = rotating_object_check(BookOpts, "Bucket5", 3200), {ok, Spcl5, LastV5} = rotating_object_check(BookOpts, "Bucket5", 1600),
ok = restart_from_blankledger(BookOptsAlt, [{"Bucket3", Spcl3, LastV3},
{"Bucket5", Spcl5, LastV5}]),
{ok, Spcl6, LastV6} = rotating_object_check(BookOpts, "Bucket6", 6400),
ok = restart_from_blankledger(BookOpts, [{"Bucket3", Spcl3, LastV3}, ok = restart_from_blankledger(BookOpts, [{"Bucket3", Spcl3, LastV3},
{"Bucket4", Spcl4, LastV4}, {"Bucket5", Spcl5, LastV5}]),
{"Bucket5", Spcl5, LastV5}, {ok, Spcl6, LastV6} = rotating_object_check(BookOpts, "Bucket6", 3200),
{"Bucket6", Spcl6, LastV6}]),
{ok, Book1} = leveled_bookie:book_start(BookOpts), {ok, Book1} = leveled_bookie:book_start(BookOpts),
compact_and_wait(Book1), compact_and_wait(Book1),
compact_and_wait(Book1),
ok = leveled_bookie:book_close(Book1), ok = leveled_bookie:book_close(Book1),
ok = restart_from_blankledger(BookOpts, [{"Bucket3", Spcl3, LastV3}, ok = restart_from_blankledger(BookOptsAlt, [{"Bucket3", Spcl3, LastV3},
{"Bucket4", Spcl4, LastV4}, {"Bucket4", Spcl4, LastV4},
{"Bucket5", Spcl5, LastV5}, {"Bucket5", Spcl5, LastV5},
{"Bucket6", Spcl6, LastV6}]), {"Bucket6", Spcl6, LastV6}]),
{ok, Book2} = leveled_bookie:book_start(BookOptsAlt), {ok, Book2} = leveled_bookie:book_start(BookOptsAlt),
compact_and_wait(Book2),
ok = leveled_bookie:book_close(Book2),
{KSpcL2, _V2} = testutil:put_indexed_objects(Book2, "AltBucket6", 3000), ok = restart_from_blankledger(BookOptsAlt, [{"Bucket3", Spcl3, LastV3},
{"Bucket4", Spcl4, LastV4},
{"Bucket5", Spcl5, LastV5},
{"Bucket6", Spcl6, LastV6}]),
{ok, Book3} = leveled_bookie:book_start(BookOptsAlt),
{KSpcL2, _V2} = testutil:put_indexed_objects(Book3, "AltBucket6", 3000),
Q2 = fun(RT) -> {index_query, Q2 = fun(RT) -> {index_query,
"AltBucket6", "AltBucket6",
{fun testutil:foldkeysfun/3, []}, {fun testutil:foldkeysfun/3, []},
{"idx1_bin", "#", "|"}, {"idx1_bin", "#", "|"},
{RT, undefined}} {RT, undefined}}
end, end,
{async, KFolder2A} = leveled_bookie:book_returnfolder(Book2, Q2(false)), {async, KFolder2A} = leveled_bookie:book_returnfolder(Book3, Q2(false)),
KeyList2A = lists:usort(KFolder2A()), KeyList2A = lists:usort(KFolder2A()),
true = length(KeyList2A) == 3000, true = length(KeyList2A) == 3000,
DeleteFun = DeleteFun =
fun({DK, [{add, DIdx, DTerm}]}) -> fun({DK, [{add, DIdx, DTerm}]}) ->
ok = testutil:book_riakdelete(Book2, ok = testutil:book_riakdelete(Book3,
"AltBucket6", "AltBucket6",
DK, DK,
[{remove, DIdx, DTerm}]) [{remove, DIdx, DTerm}])
end, end,
lists:foreach(DeleteFun, KSpcL2), lists:foreach(DeleteFun, KSpcL2),
{async, KFolder2AD} = leveled_bookie:book_returnfolder(Book2, Q2(false)),
KeyList2AD = lists:usort(KFolder2AD()),
true = length(KeyList2AD) == 0,
ok = leveled_bookie:book_close(Book2),
{ok, Book3} = leveled_bookie:book_start(BookOptsAlt),
io:format("Compact after deletions~n"),
compact_and_wait(Book3),
compact_and_wait(Book3),
{async, KFolder3AD} = leveled_bookie:book_returnfolder(Book3, Q2(false)), {async, KFolder3AD} = leveled_bookie:book_returnfolder(Book3, Q2(false)),
KeyList3AD = lists:usort(KFolder3AD()), KeyList3AD = lists:usort(KFolder3AD()),
true = length(KeyList3AD) == 0, true = length(KeyList3AD) == 0,
ok = leveled_bookie:book_close(Book3), ok = leveled_bookie:book_close(Book3),
{ok, Book4} = leveled_bookie:book_start(BookOptsAlt),
io:format("Compact after deletions~n"),
compact_and_wait(Book4),
{async, KFolder4AD} = leveled_bookie:book_returnfolder(Book4, Q2(false)),
KeyList4AD = lists:usort(KFolder4AD()),
true = length(KeyList4AD) == 0,
ok = leveled_bookie:book_close(Book4),
testutil:reset_filestructure(). testutil:reset_filestructure().
@ -845,6 +861,10 @@ rotating_object_check(BookOpts, B, NumberOfObjects) ->
B, B,
KSpcL2, KSpcL2,
false), false),
ok = testutil:check_indexed_objects(Book1,
B,
KSpcL1 ++ KSpcL2 ++ KSpcL3,
V3),
ok = leveled_bookie:book_close(Book1), ok = leveled_bookie:book_close(Book1),
{ok, Book2} = leveled_bookie:book_start(BookOpts), {ok, Book2} = leveled_bookie:book_start(BookOpts),
ok = testutil:check_indexed_objects(Book2, ok = testutil:check_indexed_objects(Book2,

View file

@ -68,6 +68,7 @@
-define(MD_VTAG, <<"X-Riak-VTag">>). -define(MD_VTAG, <<"X-Riak-VTag">>).
-define(MD_LASTMOD, <<"X-Riak-Last-Modified">>). -define(MD_LASTMOD, <<"X-Riak-Last-Modified">>).
-define(MD_DELETED, <<"X-Riak-Deleted">>). -define(MD_DELETED, <<"X-Riak-Deleted">>).
-define(MD_INDEX, <<"index">>).
-define(EMPTY_VTAG_BIN, <<"e">>). -define(EMPTY_VTAG_BIN, <<"e">>).
-define(ROOT_PATH, "test"). -define(ROOT_PATH, "test").
@ -517,23 +518,30 @@ set_object(Bucket, Key, Value, IndexGen) ->
set_object(Bucket, Key, Value, IndexGen, []). set_object(Bucket, Key, Value, IndexGen, []).
set_object(Bucket, Key, Value, IndexGen, Indexes2Remove) -> set_object(Bucket, Key, Value, IndexGen, Indexes2Remove) ->
set_object(Bucket, Key, Value, IndexGen, Indexes2Remove, []).
set_object(Bucket, Key, Value, IndexGen, Indexes2Remove, IndexesNotToRemove) ->
IdxSpecs = IndexGen(),
Indexes =
lists:map(fun({add, IdxF, IdxV}) -> {IdxF, IdxV} end,
IdxSpecs ++ IndexesNotToRemove),
Obj = {Bucket, Obj = {Bucket,
Key, Key,
Value, Value,
IndexGen() ++ lists:map(fun({add, IdxF, IdxV}) -> IdxSpecs ++
{remove, IdxF, IdxV} end, lists:map(fun({add, IdxF, IdxV}) -> {remove, IdxF, IdxV} end,
Indexes2Remove), Indexes2Remove),
[{"MDK", "MDV" ++ Key}, [{<<"MDK">>, "MDV" ++ Key},
{"MDK2", "MDV" ++ Key}, {<<"MDK2">>, "MDV" ++ Key},
{?MD_LASTMOD, os:timestamp()}]}, {?MD_LASTMOD, os:timestamp()},
{?MD_INDEX, Indexes}]},
{B1, K1, V1, Spec1, MD} = Obj, {B1, K1, V1, Spec1, MD} = Obj,
Content = #r_content{metadata=dict:from_list(MD), value=V1}, Content = #r_content{metadata=dict:from_list(MD), value=V1},
{#r_object{bucket=B1, {#r_object{bucket=B1,
key=K1, key=K1,
contents=[Content], contents=[Content],
vclock=generate_vclock()}, vclock=generate_vclock()},
Spec1}. Spec1 ++ IndexesNotToRemove}.
get_value_from_objectlistitem({_Int, Obj, _Spc}) -> get_value_from_objectlistitem({_Int, Obj, _Spc}) ->
[Content] = Obj#r_object.contents, [Content] = Obj#r_object.contents,
@ -762,26 +770,28 @@ put_altered_indexed_objects(Book, Bucket, KSpecL) ->
put_altered_indexed_objects(Book, Bucket, KSpecL, true). put_altered_indexed_objects(Book, Bucket, KSpecL, true).
put_altered_indexed_objects(Book, Bucket, KSpecL, RemoveOld2i) -> put_altered_indexed_objects(Book, Bucket, KSpecL, RemoveOld2i) ->
IndexGen = testutil:get_randomindexes_generator(1), IndexGen = get_randomindexes_generator(1),
V = testutil:get_compressiblevalue(), V = get_compressiblevalue(),
RplKSpecL = lists:map(fun({K, Spc}) -> FindAdditionFun = fun(SpcItem) -> element(1, SpcItem) == add end,
AddSpc = if MapFun =
RemoveOld2i == true -> fun({K, Spc}) ->
[lists:keyfind(add, 1, Spc)]; {RemoveSpc, AddSpc} =
RemoveOld2i == false -> case RemoveOld2i of
[] true ->
{lists:filter(FindAdditionFun, Spc), []};
false ->
{[], lists:filter(FindAdditionFun, Spc)}
end, end,
{O, AltSpc} = testutil:set_object(Bucket, {O, AltSpc} =
K, set_object(Bucket, K, V,
V, IndexGen, RemoveSpc, AddSpc),
IndexGen,
AddSpc),
case book_riakput(Book, O, AltSpc) of case book_riakput(Book, O, AltSpc) of
ok -> ok; ok -> ok;
pause -> timer:sleep(?SLOWOFFER_DELAY) pause -> timer:sleep(?SLOWOFFER_DELAY)
end, end,
{K, AltSpc} end, {K, AltSpc}
KSpecL), end,
RplKSpecL = lists:map(MapFun, KSpecL),
{RplKSpecL, V}. {RplKSpecL, V}.
rotating_object_check(RootPath, B, NumberOfObjects) -> rotating_object_check(RootPath, B, NumberOfObjects) ->
@ -790,16 +800,16 @@ rotating_object_check(RootPath, B, NumberOfObjects) ->
{max_journalsize, 5000000}, {max_journalsize, 5000000},
{sync_strategy, sync_strategy()}], {sync_strategy, sync_strategy()}],
{ok, Book1} = leveled_bookie:book_start(BookOpts), {ok, Book1} = leveled_bookie:book_start(BookOpts),
{KSpcL1, V1} = testutil:put_indexed_objects(Book1, B, NumberOfObjects), {KSpcL1, V1} = put_indexed_objects(Book1, B, NumberOfObjects),
ok = testutil:check_indexed_objects(Book1, B, KSpcL1, V1), ok = check_indexed_objects(Book1, B, KSpcL1, V1),
{KSpcL2, V2} = testutil:put_altered_indexed_objects(Book1, B, KSpcL1), {KSpcL2, V2} = put_altered_indexed_objects(Book1, B, KSpcL1),
ok = testutil:check_indexed_objects(Book1, B, KSpcL2, V2), ok = check_indexed_objects(Book1, B, KSpcL2, V2),
{KSpcL3, V3} = testutil:put_altered_indexed_objects(Book1, B, KSpcL2), {KSpcL3, V3} = put_altered_indexed_objects(Book1, B, KSpcL2),
ok = leveled_bookie:book_close(Book1), ok = leveled_bookie:book_close(Book1),
{ok, Book2} = leveled_bookie:book_start(BookOpts), {ok, Book2} = leveled_bookie:book_start(BookOpts),
ok = testutil:check_indexed_objects(Book2, B, KSpcL3, V3), ok = check_indexed_objects(Book2, B, KSpcL3, V3),
{KSpcL4, V4} = testutil:put_altered_indexed_objects(Book2, B, KSpcL3), {KSpcL4, V4} = put_altered_indexed_objects(Book2, B, KSpcL3),
ok = testutil:check_indexed_objects(Book2, B, KSpcL4, V4), ok = check_indexed_objects(Book2, B, KSpcL4, V4),
Query = {keylist, ?RIAK_TAG, B, {fun foldkeysfun/3, []}}, Query = {keylist, ?RIAK_TAG, B, {fun foldkeysfun/3, []}},
{async, BList} = leveled_bookie:book_returnfolder(Book2, Query), {async, BList} = leveled_bookie:book_returnfolder(Book2, Query),
true = NumberOfObjects == length(BList()), true = NumberOfObjects == length(BList()),