Iterator support
Add iterator support, used initially only for retrieving bucket statistics. The iterator is supported by exporting a function, and when the function is claled it will take a snapshot of the ledger, run the iterator and hten close the snapshot. This required a numbe rof underlying changes, in particular to get key comparison to work as "expected". The code had previously misunderstood how comparison worked between Erlang terms, and in particular did not account for tuple length being compared first by size of the tuple (and not just by each element in order).
This commit is contained in:
parent
d2cc07a9eb
commit
0a08867280
6 changed files with 762 additions and 202 deletions
|
@ -71,7 +71,7 @@
|
||||||
%% The Bookie should generate a series of ledger key changes from this
|
%% The Bookie should generate a series of ledger key changes from this
|
||||||
%% information, using a function passed in at startup. For Riak this will be
|
%% information, using a function passed in at startup. For Riak this will be
|
||||||
%% of the form:
|
%% of the form:
|
||||||
%% {{o, Bucket, Key},
|
%% {{o, Bucket, Key, SubKey|null},
|
||||||
%% SQN,
|
%% SQN,
|
||||||
%% {Hash, Size, {Riak_Metadata}},
|
%% {Hash, Size, {Riak_Metadata}},
|
||||||
%% {active, TS}|{tomb, TS}} or
|
%% {active, TS}|{tomb, TS}} or
|
||||||
|
@ -136,6 +136,7 @@
|
||||||
book_riakput/3,
|
book_riakput/3,
|
||||||
book_riakget/3,
|
book_riakget/3,
|
||||||
book_riakhead/3,
|
book_riakhead/3,
|
||||||
|
book_returnfolder/2,
|
||||||
book_snapshotstore/3,
|
book_snapshotstore/3,
|
||||||
book_snapshotledger/3,
|
book_snapshotledger/3,
|
||||||
book_compactjournal/2,
|
book_compactjournal/2,
|
||||||
|
@ -144,7 +145,11 @@
|
||||||
strip_to_keyseqonly/1,
|
strip_to_keyseqonly/1,
|
||||||
strip_to_seqonly/1,
|
strip_to_seqonly/1,
|
||||||
strip_to_statusonly/1,
|
strip_to_statusonly/1,
|
||||||
striphead_to_details/1]).
|
strip_to_keyseqstatusonly/1,
|
||||||
|
striphead_to_details/1,
|
||||||
|
key_compare/3,
|
||||||
|
key_dominates/2,
|
||||||
|
print_key/1]).
|
||||||
|
|
||||||
-include_lib("eunit/include/eunit.hrl").
|
-include_lib("eunit/include/eunit.hrl").
|
||||||
|
|
||||||
|
@ -172,17 +177,20 @@ book_start(Opts) ->
|
||||||
gen_server:start(?MODULE, [Opts], []).
|
gen_server:start(?MODULE, [Opts], []).
|
||||||
|
|
||||||
book_riakput(Pid, Object, IndexSpecs) ->
|
book_riakput(Pid, Object, IndexSpecs) ->
|
||||||
PrimaryKey = {o, Object#r_object.bucket, Object#r_object.key},
|
PrimaryKey = {o, Object#r_object.bucket, Object#r_object.key, null},
|
||||||
gen_server:call(Pid, {put, PrimaryKey, Object, IndexSpecs}, infinity).
|
gen_server:call(Pid, {put, PrimaryKey, Object, IndexSpecs}, infinity).
|
||||||
|
|
||||||
book_riakget(Pid, Bucket, Key) ->
|
book_riakget(Pid, Bucket, Key) ->
|
||||||
PrimaryKey = {o, Bucket, Key},
|
PrimaryKey = {o, Bucket, Key, null},
|
||||||
gen_server:call(Pid, {get, PrimaryKey}, infinity).
|
gen_server:call(Pid, {get, PrimaryKey}, infinity).
|
||||||
|
|
||||||
book_riakhead(Pid, Bucket, Key) ->
|
book_riakhead(Pid, Bucket, Key) ->
|
||||||
PrimaryKey = {o, Bucket, Key},
|
PrimaryKey = {o, Bucket, Key, null},
|
||||||
gen_server:call(Pid, {head, PrimaryKey}, infinity).
|
gen_server:call(Pid, {head, PrimaryKey}, infinity).
|
||||||
|
|
||||||
|
book_returnfolder(Pid, FolderType) ->
|
||||||
|
gen_server:call(Pid, {return_folder, FolderType}, infinity).
|
||||||
|
|
||||||
book_snapshotstore(Pid, Requestor, Timeout) ->
|
book_snapshotstore(Pid, Requestor, Timeout) ->
|
||||||
gen_server:call(Pid, {snapshot, Requestor, store, Timeout}, infinity).
|
gen_server:call(Pid, {snapshot, Requestor, store, Timeout}, infinity).
|
||||||
|
|
||||||
|
@ -307,6 +315,15 @@ handle_call({snapshot, _Requestor, SnapType, _Timeout}, _From, State) ->
|
||||||
null},
|
null},
|
||||||
State}
|
State}
|
||||||
end;
|
end;
|
||||||
|
handle_call({return_folder, FolderType}, _From, State) ->
|
||||||
|
case FolderType of
|
||||||
|
{bucket_stats, Bucket} ->
|
||||||
|
{reply,
|
||||||
|
bucket_stats(State#state.penciller,
|
||||||
|
State#state.ledger_cache,
|
||||||
|
Bucket),
|
||||||
|
State}
|
||||||
|
end;
|
||||||
handle_call({compact_journal, Timeout}, _From, State) ->
|
handle_call({compact_journal, Timeout}, _From, State) ->
|
||||||
ok = leveled_inker:ink_compactjournal(State#state.inker,
|
ok = leveled_inker:ink_compactjournal(State#state.inker,
|
||||||
self(),
|
self(),
|
||||||
|
@ -343,6 +360,26 @@ code_change(_OldVsn, State, _Extra) ->
|
||||||
%%% Internal functions
|
%%% Internal functions
|
||||||
%%%============================================================================
|
%%%============================================================================
|
||||||
|
|
||||||
|
bucket_stats(Penciller, LedgerCache, Bucket) ->
|
||||||
|
Folder = fun() ->
|
||||||
|
PCLopts = #penciller_options{start_snapshot=true,
|
||||||
|
source_penciller=Penciller},
|
||||||
|
{ok, LedgerSnapshot} = leveled_penciller:pcl_start(PCLopts),
|
||||||
|
Increment = gb_trees:to_list(LedgerCache),
|
||||||
|
ok = leveled_penciller:pcl_loadsnapshot(LedgerSnapshot,
|
||||||
|
Increment),
|
||||||
|
StartKey = {o, Bucket, null, null},
|
||||||
|
EndKey = {o, Bucket, null, null},
|
||||||
|
Acc = leveled_penciller:pcl_fetchkeys(LedgerSnapshot,
|
||||||
|
StartKey,
|
||||||
|
EndKey,
|
||||||
|
fun accumulate_size/3,
|
||||||
|
{0, 0}),
|
||||||
|
ok = leveled_penciller:pcl_close(LedgerSnapshot),
|
||||||
|
Acc
|
||||||
|
end,
|
||||||
|
{async, Folder}.
|
||||||
|
|
||||||
shutdown_wait([], _Inker) ->
|
shutdown_wait([], _Inker) ->
|
||||||
false;
|
false;
|
||||||
shutdown_wait([TopPause|Rest], Inker) ->
|
shutdown_wait([TopPause|Rest], Inker) ->
|
||||||
|
@ -411,12 +448,30 @@ strip_to_keyonly({K, _V}) -> K.
|
||||||
|
|
||||||
strip_to_keyseqonly({K, {SeqN, _, _}}) -> {K, SeqN}.
|
strip_to_keyseqonly({K, {SeqN, _, _}}) -> {K, SeqN}.
|
||||||
|
|
||||||
|
strip_to_keyseqstatusonly({K, {SeqN, St, _MD}}) -> {K, SeqN, St}.
|
||||||
|
|
||||||
strip_to_statusonly({_, {_, St, _}}) -> St.
|
strip_to_statusonly({_, {_, St, _}}) -> St.
|
||||||
|
|
||||||
strip_to_seqonly({_, {SeqN, _, _}}) -> SeqN.
|
strip_to_seqonly({_, {SeqN, _, _}}) -> SeqN.
|
||||||
|
|
||||||
striphead_to_details({SeqN, St, MD}) -> {SeqN, St, MD}.
|
striphead_to_details({SeqN, St, MD}) -> {SeqN, St, MD}.
|
||||||
|
|
||||||
|
key_dominates(LeftKey, RightKey) ->
|
||||||
|
case {LeftKey, RightKey} of
|
||||||
|
{{LK, _LVAL}, {RK, _RVAL}} when LK < RK ->
|
||||||
|
left_hand_first;
|
||||||
|
{{LK, _LVAL}, {RK, _RVAL}} when RK < LK ->
|
||||||
|
right_hand_first;
|
||||||
|
{{LK, {LSN, _LST, _LMD}}, {RK, {RSN, _RST, _RMD}}}
|
||||||
|
when LK == RK, LSN >= RSN ->
|
||||||
|
left_hand_dominant;
|
||||||
|
{{LK, {LSN, _LST, _LMD}}, {RK, {RSN, _RST, _RMD}}}
|
||||||
|
when LK == RK, LSN < RSN ->
|
||||||
|
right_hand_dominant
|
||||||
|
end.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
get_metadatas(#r_object{contents=Contents}) ->
|
get_metadatas(#r_object{contents=Contents}) ->
|
||||||
[Content#r_content.metadata || Content <- Contents].
|
[Content#r_content.metadata || Content <- Contents].
|
||||||
|
|
||||||
|
@ -435,8 +490,13 @@ hash(Obj=#r_object{}) ->
|
||||||
extract_metadata(Obj, Size) ->
|
extract_metadata(Obj, Size) ->
|
||||||
{get_metadatas(Obj), vclock(Obj), hash(Obj), Size}.
|
{get_metadatas(Obj), vclock(Obj), hash(Obj), Size}.
|
||||||
|
|
||||||
|
accumulate_size(_Key, Value, {Size, Count}) ->
|
||||||
|
{_, _, MD} = Value,
|
||||||
|
{_, _, _, ObjSize} = MD,
|
||||||
|
{Size + ObjSize, Count + 1}.
|
||||||
|
|
||||||
build_metadata_object(PrimaryKey, Head) ->
|
build_metadata_object(PrimaryKey, Head) ->
|
||||||
{o, Bucket, Key} = PrimaryKey,
|
{o, Bucket, Key, null} = PrimaryKey,
|
||||||
{MD, VC, _, _} = Head,
|
{MD, VC, _, _} = Head,
|
||||||
Contents = lists:foldl(fun(X, Acc) -> Acc ++ [#r_content{metadata=X}] end,
|
Contents = lists:foldl(fun(X, Acc) -> Acc ++ [#r_content{metadata=X}] end,
|
||||||
[],
|
[],
|
||||||
|
@ -453,12 +513,36 @@ convert_indexspecs(IndexSpecs, SQN, PrimaryKey) ->
|
||||||
%% TODO: timestamps for delayed reaping
|
%% TODO: timestamps for delayed reaping
|
||||||
{tomb, infinity}
|
{tomb, infinity}
|
||||||
end,
|
end,
|
||||||
{o, B, K} = PrimaryKey,
|
{o, B, K, _SK} = PrimaryKey,
|
||||||
{{i, B, IndexField, IndexValue, K},
|
{{i, B, {IndexField, IndexValue}, K},
|
||||||
{SQN, Status, null}}
|
{SQN, Status, null}}
|
||||||
end,
|
end,
|
||||||
IndexSpecs).
|
IndexSpecs).
|
||||||
|
|
||||||
|
% Return a tuple of string to ease the printing of keys to logs
|
||||||
|
print_key(Key) ->
|
||||||
|
case Key of
|
||||||
|
{o, B, K, _SK} ->
|
||||||
|
{"Object", B, K};
|
||||||
|
{i, B, {F, _V}, _K} ->
|
||||||
|
{"Index", B, F}
|
||||||
|
end.
|
||||||
|
|
||||||
|
% Compare a key against a query key, only comparing elements that are non-null
|
||||||
|
% in the Query key
|
||||||
|
key_compare(QueryKey, CheckingKey, gt) ->
|
||||||
|
key_compare(QueryKey, CheckingKey, fun(X,Y) -> X > Y end);
|
||||||
|
key_compare(QueryKey, CheckingKey, lt) ->
|
||||||
|
key_compare(QueryKey, CheckingKey, fun(X,Y) -> X < Y end);
|
||||||
|
key_compare({QK1, null, null, null}, {CK1, _, _, _}, CompareFun) ->
|
||||||
|
CompareFun(QK1, CK1);
|
||||||
|
key_compare({QK1, QK2, null, null}, {CK1, CK2, _, _}, CompareFun) ->
|
||||||
|
CompareFun({QK1, QK2}, {CK1, CK2});
|
||||||
|
key_compare({QK1, QK2, QK3, null}, {CK1, CK2, CK3, _}, CompareFun) ->
|
||||||
|
CompareFun({QK1, QK2, QK3}, {CK1, CK2, CK3});
|
||||||
|
key_compare(QueryKey, CheckingKey, CompareFun) ->
|
||||||
|
CompareFun(QueryKey, CheckingKey).
|
||||||
|
|
||||||
|
|
||||||
preparefor_ledgercache(PK, SQN, Obj, Size, IndexSpecs) ->
|
preparefor_ledgercache(PK, SQN, Obj, Size, IndexSpecs) ->
|
||||||
PrimaryChange = {PK,
|
PrimaryChange = {PK,
|
||||||
|
@ -628,12 +712,12 @@ indexspecs_test() ->
|
||||||
IndexSpecs = [{add, "t1_int", 456},
|
IndexSpecs = [{add, "t1_int", 456},
|
||||||
{add, "t1_bin", "adbc123"},
|
{add, "t1_bin", "adbc123"},
|
||||||
{remove, "t1_bin", "abdc456"}],
|
{remove, "t1_bin", "abdc456"}],
|
||||||
Changes = convert_indexspecs(IndexSpecs, 1, {o, "Bucket", "Key2"}),
|
Changes = convert_indexspecs(IndexSpecs, 1, {o, "Bucket", "Key2", null}),
|
||||||
?assertMatch({{i, "Bucket", "t1_int", 456, "Key2"},
|
?assertMatch({{i, "Bucket", {"t1_int", 456}, "Key2"},
|
||||||
{1, {active, infinity}, null}}, lists:nth(1, Changes)),
|
{1, {active, infinity}, null}}, lists:nth(1, Changes)),
|
||||||
?assertMatch({{i, "Bucket", "t1_bin", "adbc123", "Key2"},
|
?assertMatch({{i, "Bucket", {"t1_bin", "adbc123"}, "Key2"},
|
||||||
{1, {active, infinity}, null}}, lists:nth(2, Changes)),
|
{1, {active, infinity}, null}}, lists:nth(2, Changes)),
|
||||||
?assertMatch({{i, "Bucket", "t1_bin", "abdc456", "Key2"},
|
?assertMatch({{i, "Bucket", {"t1_bin", "abdc456"}, "Key2"},
|
||||||
{1, {tomb, infinity}, null}}, lists:nth(3, Changes)).
|
{1, {tomb, infinity}, null}}, lists:nth(3, Changes)).
|
||||||
|
|
||||||
-endif.
|
-endif.
|
|
@ -251,8 +251,8 @@ handle_call({register_snapshot, Requestor}, _From , State) ->
|
||||||
State#state{registered_snapshots=Rs}};
|
State#state{registered_snapshots=Rs}};
|
||||||
handle_call({release_snapshot, Snapshot}, _From , State) ->
|
handle_call({release_snapshot, Snapshot}, _From , State) ->
|
||||||
Rs = lists:keydelete(Snapshot, 1, State#state.registered_snapshots),
|
Rs = lists:keydelete(Snapshot, 1, State#state.registered_snapshots),
|
||||||
io:format("Snapshot ~w released~n", [Snapshot]),
|
io:format("Ledger snapshot ~w released~n", [Snapshot]),
|
||||||
io:format("Remaining snapshots are ~w~n", [Rs]),
|
io:format("Remaining ledger snapshots are ~w~n", [Rs]),
|
||||||
{reply, ok, State#state{registered_snapshots=Rs}};
|
{reply, ok, State#state{registered_snapshots=Rs}};
|
||||||
handle_call(get_manifest, _From, State) ->
|
handle_call(get_manifest, _From, State) ->
|
||||||
{reply, State#state.manifest, State};
|
{reply, State#state.manifest, State};
|
||||||
|
|
|
@ -100,6 +100,7 @@ init([]) ->
|
||||||
handle_call({register, Owner}, _From, State) ->
|
handle_call({register, Owner}, _From, State) ->
|
||||||
{reply, ok, State#state{owner=Owner}, ?INACTIVITY_TIMEOUT};
|
{reply, ok, State#state{owner=Owner}, ?INACTIVITY_TIMEOUT};
|
||||||
handle_call({manifest_change, return, true}, _From, State) ->
|
handle_call({manifest_change, return, true}, _From, State) ->
|
||||||
|
io:format("Request for manifest change from clerk on closing~n"),
|
||||||
case State#state.change_pending of
|
case State#state.change_pending of
|
||||||
true ->
|
true ->
|
||||||
WI = State#state.work_item,
|
WI = State#state.work_item,
|
||||||
|
@ -110,11 +111,13 @@ handle_call({manifest_change, return, true}, _From, State) ->
|
||||||
handle_call({manifest_change, confirm, Closing}, From, State) ->
|
handle_call({manifest_change, confirm, Closing}, From, State) ->
|
||||||
case Closing of
|
case Closing of
|
||||||
true ->
|
true ->
|
||||||
|
io:format("Confirmation of manifest change on closing~n"),
|
||||||
WI = State#state.work_item,
|
WI = State#state.work_item,
|
||||||
ok = mark_for_delete(WI#penciller_work.unreferenced_files,
|
ok = mark_for_delete(WI#penciller_work.unreferenced_files,
|
||||||
State#state.owner),
|
State#state.owner),
|
||||||
{stop, normal, ok, State};
|
{stop, normal, ok, State};
|
||||||
false ->
|
false ->
|
||||||
|
io:format("Prompted confirmation of manifest change~n"),
|
||||||
gen_server:reply(From, ok),
|
gen_server:reply(From, ok),
|
||||||
WI = State#state.work_item,
|
WI = State#state.work_item,
|
||||||
mark_for_delete(WI#penciller_work.unreferenced_files,
|
mark_for_delete(WI#penciller_work.unreferenced_files,
|
||||||
|
@ -168,6 +171,7 @@ requestandhandle_work(State) ->
|
||||||
{NewManifest, FilesToDelete} = merge(WI),
|
{NewManifest, FilesToDelete} = merge(WI),
|
||||||
UpdWI = WI#penciller_work{new_manifest=NewManifest,
|
UpdWI = WI#penciller_work{new_manifest=NewManifest,
|
||||||
unreferenced_files=FilesToDelete},
|
unreferenced_files=FilesToDelete},
|
||||||
|
io:format("Clerk prompting Penciller regarding manifest change~n"),
|
||||||
ok = leveled_penciller:pcl_promptmanifestchange(State#state.owner,
|
ok = leveled_penciller:pcl_promptmanifestchange(State#state.owner,
|
||||||
UpdWI),
|
UpdWI),
|
||||||
{true, UpdWI}
|
{true, UpdWI}
|
||||||
|
|
|
@ -234,12 +234,14 @@
|
||||||
pcl_start/1,
|
pcl_start/1,
|
||||||
pcl_pushmem/2,
|
pcl_pushmem/2,
|
||||||
pcl_fetch/2,
|
pcl_fetch/2,
|
||||||
|
pcl_fetchkeys/5,
|
||||||
pcl_checksequencenumber/3,
|
pcl_checksequencenumber/3,
|
||||||
pcl_workforclerk/1,
|
pcl_workforclerk/1,
|
||||||
pcl_promptmanifestchange/2,
|
pcl_promptmanifestchange/2,
|
||||||
pcl_confirmdelete/2,
|
pcl_confirmdelete/2,
|
||||||
pcl_close/1,
|
pcl_close/1,
|
||||||
pcl_registersnapshot/2,
|
pcl_registersnapshot/2,
|
||||||
|
pcl_releasesnapshot/2,
|
||||||
pcl_updatesnapshotcache/3,
|
pcl_updatesnapshotcache/3,
|
||||||
pcl_loadsnapshot/2,
|
pcl_loadsnapshot/2,
|
||||||
pcl_getstartupsequencenumber/1,
|
pcl_getstartupsequencenumber/1,
|
||||||
|
@ -284,7 +286,6 @@
|
||||||
snapshot_fully_loaded = false :: boolean(),
|
snapshot_fully_loaded = false :: boolean(),
|
||||||
source_penciller :: pid()}).
|
source_penciller :: pid()}).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
%%%============================================================================
|
%%%============================================================================
|
||||||
%%% API
|
%%% API
|
||||||
|
@ -301,6 +302,11 @@ pcl_pushmem(Pid, DumpList) ->
|
||||||
pcl_fetch(Pid, Key) ->
|
pcl_fetch(Pid, Key) ->
|
||||||
gen_server:call(Pid, {fetch, Key}, infinity).
|
gen_server:call(Pid, {fetch, Key}, infinity).
|
||||||
|
|
||||||
|
pcl_fetchkeys(Pid, StartKey, EndKey, AccFun, InitAcc) ->
|
||||||
|
gen_server:call(Pid,
|
||||||
|
{fetch_keys, StartKey, EndKey, AccFun, InitAcc},
|
||||||
|
infinity).
|
||||||
|
|
||||||
pcl_checksequencenumber(Pid, Key, SQN) ->
|
pcl_checksequencenumber(Pid, Key, SQN) ->
|
||||||
gen_server:call(Pid, {check_sqn, Key, SQN}, infinity).
|
gen_server:call(Pid, {check_sqn, Key, SQN}, infinity).
|
||||||
|
|
||||||
|
@ -319,6 +325,9 @@ pcl_getstartupsequencenumber(Pid) ->
|
||||||
pcl_registersnapshot(Pid, Snapshot) ->
|
pcl_registersnapshot(Pid, Snapshot) ->
|
||||||
gen_server:call(Pid, {register_snapshot, Snapshot}, infinity).
|
gen_server:call(Pid, {register_snapshot, Snapshot}, infinity).
|
||||||
|
|
||||||
|
pcl_releasesnapshot(Pid, Snapshot) ->
|
||||||
|
gen_server:cast(Pid, {release_snapshot, Snapshot}).
|
||||||
|
|
||||||
pcl_updatesnapshotcache(Pid, Tree, SQN) ->
|
pcl_updatesnapshotcache(Pid, Tree, SQN) ->
|
||||||
gen_server:cast(Pid, {update_snapshotcache, Tree, SQN}).
|
gen_server:cast(Pid, {update_snapshotcache, Tree, SQN}).
|
||||||
|
|
||||||
|
@ -476,6 +485,16 @@ handle_call({check_sqn, Key, SQN},
|
||||||
State#state.levelzero_snapshot),
|
State#state.levelzero_snapshot),
|
||||||
SQN),
|
SQN),
|
||||||
State};
|
State};
|
||||||
|
handle_call({fetch_keys, StartKey, EndKey, AccFun, InitAcc},
|
||||||
|
_From,
|
||||||
|
State=#state{snapshot_fully_loaded=Ready})
|
||||||
|
when Ready == true ->
|
||||||
|
L0iter = gb_trees:iterator_from(StartKey, State#state.levelzero_snapshot),
|
||||||
|
SFTiter = initiate_rangequery_frommanifest(StartKey,
|
||||||
|
EndKey,
|
||||||
|
State#state.manifest),
|
||||||
|
Acc = keyfolder(L0iter, SFTiter, StartKey, EndKey, {AccFun, InitAcc}),
|
||||||
|
{reply, Acc, State};
|
||||||
handle_call(work_for_clerk, From, State) ->
|
handle_call(work_for_clerk, From, State) ->
|
||||||
{UpdState, Work} = return_work(State, From),
|
{UpdState, Work} = return_work(State, From),
|
||||||
{reply, {Work, UpdState#state.backlog}, UpdState};
|
{reply, {Work, UpdState#state.backlog}, UpdState};
|
||||||
|
@ -498,7 +517,10 @@ handle_call({load_snapshot, Increment}, _From, State) ->
|
||||||
TreeSQN0 > MemTableCopy#l0snapshot.ledger_sqn ->
|
TreeSQN0 > MemTableCopy#l0snapshot.ledger_sqn ->
|
||||||
pcl_updatesnapshotcache(State#state.source_penciller,
|
pcl_updatesnapshotcache(State#state.source_penciller,
|
||||||
Tree0,
|
Tree0,
|
||||||
TreeSQN0)
|
TreeSQN0);
|
||||||
|
true ->
|
||||||
|
io:format("No update required to snapshot cache~n"),
|
||||||
|
ok
|
||||||
end,
|
end,
|
||||||
{Tree1, TreeSQN1} = roll_new_tree(Tree0, [Increment], TreeSQN0),
|
{Tree1, TreeSQN1} = roll_new_tree(Tree0, [Increment], TreeSQN0),
|
||||||
io:format("Snapshot loaded to start at SQN~w~n", [TreeSQN1]),
|
io:format("Snapshot loaded to start at SQN~w~n", [TreeSQN1]),
|
||||||
|
@ -517,15 +539,20 @@ handle_cast({manifest_change, WI}, State) ->
|
||||||
confirm,
|
confirm,
|
||||||
false),
|
false),
|
||||||
{noreply, UpdState};
|
{noreply, UpdState};
|
||||||
handle_cast(_Msg, State) ->
|
handle_cast({release_snapshot, Snapshot}, State) ->
|
||||||
{noreply, State}.
|
Rs = lists:keydelete(Snapshot, 1, State#state.registered_snapshots),
|
||||||
|
io:format("Penciller snapshot ~w released~n", [Snapshot]),
|
||||||
|
{noreply, State#state{registered_snapshots=Rs}}.
|
||||||
|
|
||||||
handle_info(_Info, State) ->
|
handle_info(_Info, State) ->
|
||||||
{noreply, State}.
|
{noreply, State}.
|
||||||
|
|
||||||
terminate(_Reason, _State=#state{is_snapshot=Snap}) when Snap == true ->
|
terminate(Reason, State=#state{is_snapshot=Snap}) when Snap == true ->
|
||||||
|
ok = pcl_releasesnapshot(State#state.source_penciller, self()),
|
||||||
|
io:format("Sent release message for snapshot following close for "
|
||||||
|
++ "reason ~w~n", [Reason]),
|
||||||
ok;
|
ok;
|
||||||
terminate(_Reason, State) ->
|
terminate(Reason, State) ->
|
||||||
%% When a Penciller shuts down it isn't safe to try an manage the safe
|
%% When a Penciller shuts down it isn't safe to try an manage the safe
|
||||||
%% finishing of any outstanding work. The last commmitted manifest will
|
%% finishing of any outstanding work. The last commmitted manifest will
|
||||||
%% be used.
|
%% be used.
|
||||||
|
@ -542,6 +569,7 @@ terminate(_Reason, State) ->
|
||||||
%% The cast may not succeed as the clerk could be synchronously calling
|
%% The cast may not succeed as the clerk could be synchronously calling
|
||||||
%% the penciller looking for a manifest commit
|
%% the penciller looking for a manifest commit
|
||||||
%%
|
%%
|
||||||
|
io:format("Penciller closing for reason - ~w~n", [Reason]),
|
||||||
MC = leveled_pclerk:clerk_manifestchange(State#state.clerk,
|
MC = leveled_pclerk:clerk_manifestchange(State#state.clerk,
|
||||||
return,
|
return,
|
||||||
true),
|
true),
|
||||||
|
@ -976,19 +1004,216 @@ print_manifest(Manifest) ->
|
||||||
io:format("Manifest at Level ~w~n", [L]),
|
io:format("Manifest at Level ~w~n", [L]),
|
||||||
Level = get_item(L, Manifest, []),
|
Level = get_item(L, Manifest, []),
|
||||||
lists:foreach(fun(M) ->
|
lists:foreach(fun(M) ->
|
||||||
{_, SB, SK} = M#manifest_entry.start_key,
|
print_manifest_entry(M) end,
|
||||||
{_, EB, EK} = M#manifest_entry.end_key,
|
|
||||||
io:format("Manifest entry of " ++
|
|
||||||
"startkey ~s ~s " ++
|
|
||||||
"endkey ~s ~s " ++
|
|
||||||
"filename=~s~n",
|
|
||||||
[SB, SK, EB, EK,
|
|
||||||
M#manifest_entry.filename])
|
|
||||||
end,
|
|
||||||
Level)
|
Level)
|
||||||
end,
|
end,
|
||||||
lists:seq(1, ?MAX_LEVELS - 1)).
|
lists:seq(1, ?MAX_LEVELS - 1)).
|
||||||
|
|
||||||
|
print_manifest_entry(Entry) ->
|
||||||
|
{S1, S2, S3} = leveled_bookie:print_key(Entry#manifest_entry.start_key),
|
||||||
|
{E1, E2, E3} = leveled_bookie:print_key(Entry#manifest_entry.end_key),
|
||||||
|
io:format("Manifest entry of " ++
|
||||||
|
"startkey ~s ~s ~s " ++
|
||||||
|
"endkey ~s ~s ~s " ++
|
||||||
|
"filename=~s~n",
|
||||||
|
[S1, S2, S3, E1, E2, E3,
|
||||||
|
Entry#manifest_entry.filename]).
|
||||||
|
|
||||||
|
initiate_rangequery_frommanifest(StartKey, EndKey, Manifest) ->
|
||||||
|
CompareFun = fun(M) ->
|
||||||
|
C1 = leveled_bookie:key_compare(StartKey,
|
||||||
|
M#manifest_entry.end_key,
|
||||||
|
gt),
|
||||||
|
C2 = leveled_bookie:key_compare(EndKey,
|
||||||
|
M#manifest_entry.start_key,
|
||||||
|
lt),
|
||||||
|
not (C1 or C2) end,
|
||||||
|
lists:foldl(fun(L, AccL) ->
|
||||||
|
Level = get_item(L, Manifest, []),
|
||||||
|
FL = lists:foldl(fun(M, Acc) ->
|
||||||
|
case CompareFun(M) of
|
||||||
|
true ->
|
||||||
|
Acc ++ [{next_file, M}];
|
||||||
|
false ->
|
||||||
|
Acc
|
||||||
|
end end,
|
||||||
|
[],
|
||||||
|
Level),
|
||||||
|
case FL of
|
||||||
|
[] -> AccL;
|
||||||
|
FL -> AccL ++ [{L, FL}]
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
[],
|
||||||
|
lists:seq(1, ?MAX_LEVELS - 1)).
|
||||||
|
|
||||||
|
%% Looks to find the best choice for the next key across the levels (other
|
||||||
|
%% than in-memory table)
|
||||||
|
%% In finding the best choice, the next key in a given level may be a next
|
||||||
|
%% block or next file pointer which will need to be expanded
|
||||||
|
|
||||||
|
find_nextkey(QueryArray, StartKey, EndKey) ->
|
||||||
|
find_nextkey(QueryArray,
|
||||||
|
1,
|
||||||
|
{null, null},
|
||||||
|
{fun leveled_sft:sft_getkvrange/4, StartKey, EndKey, 1}).
|
||||||
|
|
||||||
|
find_nextkey(_QueryArray, LCnt, {null, null}, _QueryFunT)
|
||||||
|
when LCnt > ?MAX_LEVELS ->
|
||||||
|
% The array has been scanned wihtout finding a best key - must be
|
||||||
|
% exhausted - respond to indicate no more keys to be found by the
|
||||||
|
% iterator
|
||||||
|
no_more_keys;
|
||||||
|
find_nextkey(QueryArray, LCnt, {BKL, BestKV}, _QueryFunT)
|
||||||
|
when LCnt > ?MAX_LEVELS ->
|
||||||
|
% All levels have been scanned, so need to remove the best result from
|
||||||
|
% the array, and return that array along with the best key/sqn/status
|
||||||
|
% combination
|
||||||
|
{BKL, [BestKV|Tail]} = lists:keyfind(BKL, 1, QueryArray),
|
||||||
|
{lists:keyreplace(BKL, 1, QueryArray, {BKL, Tail}), BestKV};
|
||||||
|
find_nextkey(QueryArray, LCnt, {BestKeyLevel, BestKV}, QueryFunT) ->
|
||||||
|
% Get the next key at this level
|
||||||
|
{NextKey, RestOfKeys} = case lists:keyfind(LCnt, 1, QueryArray) of
|
||||||
|
false ->
|
||||||
|
{null, null};
|
||||||
|
{LCnt, []} ->
|
||||||
|
{null, null};
|
||||||
|
{LCnt, [NK|ROfKs]} ->
|
||||||
|
{NK, ROfKs}
|
||||||
|
end,
|
||||||
|
% Compare the next key at this level with the best key
|
||||||
|
case {NextKey, BestKeyLevel, BestKV} of
|
||||||
|
{null, BKL, BKV} ->
|
||||||
|
% There is no key at this level - go to the next level
|
||||||
|
find_nextkey(QueryArray, LCnt + 1, {BKL, BKV}, QueryFunT);
|
||||||
|
{{next_file, ManifestEntry}, BKL, BKV} ->
|
||||||
|
% The first key at this level is pointer to a file - need to query
|
||||||
|
% the file to expand this level out before proceeding
|
||||||
|
Owner = ManifestEntry#manifest_entry.owner,
|
||||||
|
{QueryFun, StartKey, EndKey, ScanSize} = QueryFunT,
|
||||||
|
QueryResult = QueryFun(Owner, StartKey, EndKey, ScanSize),
|
||||||
|
NewEntry = {LCnt, QueryResult ++ RestOfKeys},
|
||||||
|
% Need to loop around at this level (LCnt) as we have not yet
|
||||||
|
% examined a real key at this level
|
||||||
|
find_nextkey(lists:keyreplace(LCnt, 1, QueryArray, NewEntry),
|
||||||
|
LCnt,
|
||||||
|
{BKL, BKV},
|
||||||
|
QueryFunT);
|
||||||
|
{{next, SFTpid, NewStartKey}, BKL, BKV} ->
|
||||||
|
% The first key at this level is pointer within a file - need to
|
||||||
|
% query the file to expand this level out before proceeding
|
||||||
|
{QueryFun, _StartKey, EndKey, ScanSize} = QueryFunT,
|
||||||
|
QueryResult = QueryFun(SFTpid, NewStartKey, EndKey, ScanSize),
|
||||||
|
NewEntry = {LCnt, QueryResult ++ RestOfKeys},
|
||||||
|
% Need to loop around at this level (LCnt) as we have not yet
|
||||||
|
% examined a real key at this level
|
||||||
|
find_nextkey(lists:keyreplace(LCnt, 1, QueryArray, NewEntry),
|
||||||
|
LCnt,
|
||||||
|
{BKL, BKV},
|
||||||
|
QueryFunT);
|
||||||
|
{{Key, Val}, null, null} ->
|
||||||
|
% No best key set - so can assume that this key is the best key,
|
||||||
|
% and check the higher levels
|
||||||
|
find_nextkey(QueryArray,
|
||||||
|
LCnt + 1,
|
||||||
|
{LCnt, {Key, Val}},
|
||||||
|
QueryFunT);
|
||||||
|
{{Key, Val}, _BKL, {BestKey, _BestVal}} when Key < BestKey ->
|
||||||
|
% There is a real key and a best key to compare, and the real key
|
||||||
|
% at this level is before the best key, and so is now the new best
|
||||||
|
% key
|
||||||
|
% The QueryArray is not modified until we have checked all levels
|
||||||
|
find_nextkey(QueryArray,
|
||||||
|
LCnt + 1,
|
||||||
|
{LCnt, {Key, Val}},
|
||||||
|
QueryFunT);
|
||||||
|
{{Key, Val}, BKL, {BestKey, BestVal}} when Key == BestKey ->
|
||||||
|
SQN = leveled_bookie:strip_to_seqonly({Key, Val}),
|
||||||
|
BestSQN = leveled_bookie:strip_to_seqonly({BestKey, BestVal}),
|
||||||
|
if
|
||||||
|
SQN =< BestSQN ->
|
||||||
|
% This is a dominated key, so we need to skip over it
|
||||||
|
NewEntry = {LCnt, RestOfKeys},
|
||||||
|
find_nextkey(lists:keyreplace(LCnt, 1, QueryArray, NewEntry),
|
||||||
|
LCnt + 1,
|
||||||
|
{BKL, {BestKey, BestVal}},
|
||||||
|
QueryFunT);
|
||||||
|
SQN > BestSQN ->
|
||||||
|
% There is a real key at the front of this level and it has
|
||||||
|
% a higher SQN than the best key, so we should use this as
|
||||||
|
% the best key
|
||||||
|
% But we also need to remove the dominated key from the
|
||||||
|
% lower level in the query array
|
||||||
|
io:format("Key at level ~w with SQN ~w is better than " ++
|
||||||
|
"key at lower level ~w with SQN ~w~n",
|
||||||
|
[LCnt, SQN, BKL, BestSQN]),
|
||||||
|
OldBestEntry = lists:keyfind(BKL, 1, QueryArray),
|
||||||
|
{BKL, [{BestKey, BestVal}|BestTail]} = OldBestEntry,
|
||||||
|
find_nextkey(lists:keyreplace(BKL,
|
||||||
|
1,
|
||||||
|
QueryArray,
|
||||||
|
{BKL, BestTail}),
|
||||||
|
LCnt + 1,
|
||||||
|
{LCnt, {Key, Val}},
|
||||||
|
QueryFunT)
|
||||||
|
end;
|
||||||
|
{_, BKL, BKV} ->
|
||||||
|
% This is not the best key
|
||||||
|
find_nextkey(QueryArray, LCnt + 1, {BKL, BKV}, QueryFunT)
|
||||||
|
end.
|
||||||
|
|
||||||
|
|
||||||
|
keyfolder(null, SFTiterator, StartKey, EndKey, {AccFun, Acc}) ->
|
||||||
|
case find_nextkey(SFTiterator, StartKey, EndKey) of
|
||||||
|
no_more_keys ->
|
||||||
|
Acc;
|
||||||
|
{NxtSFTiterator, {SFTKey, SFTVal}} ->
|
||||||
|
Acc1 = AccFun(SFTKey, SFTVal, Acc),
|
||||||
|
keyfolder(null, NxtSFTiterator, StartKey, EndKey, {AccFun, Acc1})
|
||||||
|
end;
|
||||||
|
keyfolder(IMMiterator, SFTiterator, StartKey, EndKey, {AccFun, Acc}) ->
|
||||||
|
case gb_trees:next(IMMiterator) of
|
||||||
|
none ->
|
||||||
|
% There are no more keys in the in-memory iterator, so now
|
||||||
|
% iterate only over the remaining keys in the SFT iterator
|
||||||
|
keyfolder(null, SFTiterator, StartKey, EndKey, {AccFun, Acc});
|
||||||
|
{IMMKey, IMMVal, NxtIMMiterator} ->
|
||||||
|
case {leveled_bookie:key_compare(EndKey, IMMKey, lt),
|
||||||
|
find_nextkey(SFTiterator, StartKey, EndKey)} of
|
||||||
|
{true, _} ->
|
||||||
|
% There are no more keys in-range in the in-memory
|
||||||
|
% iterator, so take action as if this iterator is empty
|
||||||
|
% (see above)
|
||||||
|
keyfolder(null, SFTiterator,
|
||||||
|
StartKey, EndKey, {AccFun, Acc});
|
||||||
|
{false, no_more_keys} ->
|
||||||
|
% No more keys in range in the persisted store, so use the
|
||||||
|
% in-memory KV as the next
|
||||||
|
Acc1 = AccFun(IMMKey, IMMVal, Acc),
|
||||||
|
keyfolder(NxtIMMiterator, SFTiterator,
|
||||||
|
StartKey, EndKey, {AccFun, Acc1});
|
||||||
|
{false, {NxtSFTiterator, {SFTKey, SFTVal}}} ->
|
||||||
|
% There is a next key, so need to know which is the next
|
||||||
|
% key between the two (and handle two keys with different
|
||||||
|
% sequence numbers).
|
||||||
|
case leveled_bookie:key_dominates({IMMKey, IMMVal},
|
||||||
|
{SFTKey, SFTVal}) of
|
||||||
|
left_hand_first ->
|
||||||
|
Acc1 = AccFun(IMMKey, IMMVal, Acc),
|
||||||
|
keyfolder(NxtIMMiterator, SFTiterator,
|
||||||
|
StartKey, EndKey, {AccFun, Acc1});
|
||||||
|
right_hand_first ->
|
||||||
|
Acc1 = AccFun(SFTKey, SFTVal, Acc),
|
||||||
|
keyfolder(IMMiterator, NxtSFTiterator,
|
||||||
|
StartKey, EndKey, {AccFun, Acc1});
|
||||||
|
left_hand_dominant ->
|
||||||
|
Acc1 = AccFun(IMMKey, IMMVal, Acc),
|
||||||
|
keyfolder(NxtIMMiterator, NxtSFTiterator,
|
||||||
|
StartKey, EndKey, {AccFun, Acc1})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end.
|
||||||
|
|
||||||
|
|
||||||
assess_workqueue(WorkQ, ?MAX_LEVELS - 1, _Manifest) ->
|
assess_workqueue(WorkQ, ?MAX_LEVELS - 1, _Manifest) ->
|
||||||
WorkQ;
|
WorkQ;
|
||||||
|
@ -1069,8 +1294,14 @@ commit_manifest_change(ReturnedWorkItem, State) ->
|
||||||
|
|
||||||
|
|
||||||
rename_manifest_files(RootPath, NewMSN) ->
|
rename_manifest_files(RootPath, NewMSN) ->
|
||||||
file:rename(filepath(RootPath, NewMSN, pending_manifest),
|
OldFN = filepath(RootPath, NewMSN, pending_manifest),
|
||||||
filepath(RootPath, NewMSN, current_manifest)).
|
NewFN = filepath(RootPath, NewMSN, current_manifest),
|
||||||
|
io:format("Rename of manifest from ~s ~w to ~s ~w~n",
|
||||||
|
[OldFN,
|
||||||
|
filelib:is_file(OldFN),
|
||||||
|
NewFN,
|
||||||
|
filelib:is_file(NewFN)]),
|
||||||
|
file:rename(OldFN,NewFN).
|
||||||
|
|
||||||
filepath(RootPath, manifest) ->
|
filepath(RootPath, manifest) ->
|
||||||
RootPath ++ "/" ++ ?MANIFEST_FP;
|
RootPath ++ "/" ++ ?MANIFEST_FP;
|
||||||
|
@ -1152,20 +1383,27 @@ clean_subdir(DirPath) ->
|
||||||
end.
|
end.
|
||||||
|
|
||||||
compaction_work_assessment_test() ->
|
compaction_work_assessment_test() ->
|
||||||
L0 = [{{o, "B1", "K1"}, {o, "B3", "K3"}, dummy_pid}],
|
L0 = [{{o, "B1", "K1", null}, {o, "B3", "K3", null}, dummy_pid}],
|
||||||
L1 = [{{o, "B1", "K1"}, {o, "B2", "K2"}, dummy_pid},
|
L1 = [{{o, "B1", "K1", null}, {o, "B2", "K2", null}, dummy_pid},
|
||||||
{{o, "B2", "K3"}, {o, "B4", "K4"}, dummy_pid}],
|
{{o, "B2", "K3", null}, {o, "B4", "K4", null}, dummy_pid}],
|
||||||
Manifest = [{0, L0}, {1, L1}],
|
Manifest = [{0, L0}, {1, L1}],
|
||||||
WorkQ1 = assess_workqueue([], 0, Manifest),
|
WorkQ1 = assess_workqueue([], 0, Manifest),
|
||||||
?assertMatch(WorkQ1, [{0, Manifest}]),
|
?assertMatch(WorkQ1, [{0, Manifest}]),
|
||||||
L1Alt = lists:append(L1,
|
L1Alt = lists:append(L1,
|
||||||
[{{o, "B5", "K0001"}, {o, "B5", "K9999"}, dummy_pid},
|
[{{o, "B5", "K0001", null}, {o, "B5", "K9999", null},
|
||||||
{{o, "B6", "K0001"}, {o, "B6", "K9999"}, dummy_pid},
|
dummy_pid},
|
||||||
{{o, "B7", "K0001"}, {o, "B7", "K9999"}, dummy_pid},
|
{{o, "B6", "K0001", null}, {o, "B6", "K9999", null},
|
||||||
{{o, "B8", "K0001"}, {o, "B8", "K9999"}, dummy_pid},
|
dummy_pid},
|
||||||
{{o, "B9", "K0001"}, {o, "B9", "K9999"}, dummy_pid},
|
{{o, "B7", "K0001", null}, {o, "B7", "K9999", null},
|
||||||
{{o, "BA", "K0001"}, {o, "BA", "K9999"}, dummy_pid},
|
dummy_pid},
|
||||||
{{o, "BB", "K0001"}, {o, "BB", "K9999"}, dummy_pid}]),
|
{{o, "B8", "K0001", null}, {o, "B8", "K9999", null},
|
||||||
|
dummy_pid},
|
||||||
|
{{o, "B9", "K0001", null}, {o, "B9", "K9999", null},
|
||||||
|
dummy_pid},
|
||||||
|
{{o, "BA", "K0001", null}, {o, "BA", "K9999", null},
|
||||||
|
dummy_pid},
|
||||||
|
{{o, "BB", "K0001", null}, {o, "BB", "K9999", null},
|
||||||
|
dummy_pid}]),
|
||||||
Manifest3 = [{0, []}, {1, L1Alt}],
|
Manifest3 = [{0, []}, {1, L1Alt}],
|
||||||
WorkQ3 = assess_workqueue([], 0, Manifest3),
|
WorkQ3 = assess_workqueue([], 0, Manifest3),
|
||||||
?assertMatch(WorkQ3, [{1, Manifest3}]).
|
?assertMatch(WorkQ3, [{1, Manifest3}]).
|
||||||
|
@ -1199,26 +1437,26 @@ simple_server_test() ->
|
||||||
clean_testdir(RootPath),
|
clean_testdir(RootPath),
|
||||||
{ok, PCL} = pcl_start(#penciller_options{root_path=RootPath,
|
{ok, PCL} = pcl_start(#penciller_options{root_path=RootPath,
|
||||||
max_inmemory_tablesize=1000}),
|
max_inmemory_tablesize=1000}),
|
||||||
Key1 = {{o,"Bucket0001", "Key0001"}, {1, {active, infinity}, null}},
|
Key1 = {{o,"Bucket0001", "Key0001", null}, {1, {active, infinity}, null}},
|
||||||
KL1 = lists:sort(leveled_sft:generate_randomkeys({1000, 2})),
|
KL1 = lists:sort(leveled_sft:generate_randomkeys({1000, 2})),
|
||||||
Key2 = {{o,"Bucket0002", "Key0002"}, {1002, {active, infinity}, null}},
|
Key2 = {{o,"Bucket0002", "Key0002", null}, {1002, {active, infinity}, null}},
|
||||||
KL2 = lists:sort(leveled_sft:generate_randomkeys({1000, 1002})),
|
KL2 = lists:sort(leveled_sft:generate_randomkeys({1000, 1002})),
|
||||||
Key3 = {{o,"Bucket0003", "Key0003"}, {2002, {active, infinity}, null}},
|
Key3 = {{o,"Bucket0003", "Key0003", null}, {2002, {active, infinity}, null}},
|
||||||
KL3 = lists:sort(leveled_sft:generate_randomkeys({1000, 2002})),
|
KL3 = lists:sort(leveled_sft:generate_randomkeys({1000, 2002})),
|
||||||
Key4 = {{o,"Bucket0004", "Key0004"}, {3002, {active, infinity}, null}},
|
Key4 = {{o,"Bucket0004", "Key0004", null}, {3002, {active, infinity}, null}},
|
||||||
KL4 = lists:sort(leveled_sft:generate_randomkeys({1000, 3002})),
|
KL4 = lists:sort(leveled_sft:generate_randomkeys({1000, 3002})),
|
||||||
ok = pcl_pushmem(PCL, [Key1]),
|
ok = pcl_pushmem(PCL, [Key1]),
|
||||||
?assertMatch(Key1, pcl_fetch(PCL, {o,"Bucket0001", "Key0001"})),
|
?assertMatch(Key1, pcl_fetch(PCL, {o,"Bucket0001", "Key0001", null})),
|
||||||
ok = pcl_pushmem(PCL, KL1),
|
ok = pcl_pushmem(PCL, KL1),
|
||||||
?assertMatch(Key1, pcl_fetch(PCL, {o,"Bucket0001", "Key0001"})),
|
?assertMatch(Key1, pcl_fetch(PCL, {o,"Bucket0001", "Key0001", null})),
|
||||||
maybe_pause_push(pcl_pushmem(PCL, [Key2])),
|
maybe_pause_push(pcl_pushmem(PCL, [Key2])),
|
||||||
?assertMatch(Key1, pcl_fetch(PCL, {o,"Bucket0001", "Key0001"})),
|
?assertMatch(Key1, pcl_fetch(PCL, {o,"Bucket0001", "Key0001", null})),
|
||||||
?assertMatch(Key2, pcl_fetch(PCL, {o,"Bucket0002", "Key0002"})),
|
?assertMatch(Key2, pcl_fetch(PCL, {o,"Bucket0002", "Key0002", null})),
|
||||||
maybe_pause_push(pcl_pushmem(PCL, KL2)),
|
maybe_pause_push(pcl_pushmem(PCL, KL2)),
|
||||||
maybe_pause_push(pcl_pushmem(PCL, [Key3])),
|
maybe_pause_push(pcl_pushmem(PCL, [Key3])),
|
||||||
?assertMatch(Key1, pcl_fetch(PCL, {o,"Bucket0001", "Key0001"})),
|
?assertMatch(Key1, pcl_fetch(PCL, {o,"Bucket0001", "Key0001", null})),
|
||||||
?assertMatch(Key2, pcl_fetch(PCL, {o,"Bucket0002", "Key0002"})),
|
?assertMatch(Key2, pcl_fetch(PCL, {o,"Bucket0002", "Key0002", null})),
|
||||||
?assertMatch(Key3, pcl_fetch(PCL, {o,"Bucket0003", "Key0003"})),
|
?assertMatch(Key3, pcl_fetch(PCL, {o,"Bucket0003", "Key0003", null})),
|
||||||
ok = pcl_close(PCL),
|
ok = pcl_close(PCL),
|
||||||
{ok, PCLr} = pcl_start(#penciller_options{root_path=RootPath,
|
{ok, PCLr} = pcl_start(#penciller_options{root_path=RootPath,
|
||||||
max_inmemory_tablesize=1000}),
|
max_inmemory_tablesize=1000}),
|
||||||
|
@ -1233,61 +1471,86 @@ simple_server_test() ->
|
||||||
%% everything got persisted
|
%% everything got persisted
|
||||||
ok;
|
ok;
|
||||||
_ ->
|
_ ->
|
||||||
io:format("Unexpected sequence number on restart ~w~n", [TopSQN]),
|
io:format("Unexpected sequence number on restart ~w~n",
|
||||||
|
[TopSQN]),
|
||||||
error
|
error
|
||||||
end,
|
end,
|
||||||
?assertMatch(ok, Check),
|
?assertMatch(ok, Check),
|
||||||
?assertMatch(Key1, pcl_fetch(PCLr, {o,"Bucket0001", "Key0001"})),
|
?assertMatch(Key1, pcl_fetch(PCLr, {o,"Bucket0001", "Key0001", null})),
|
||||||
?assertMatch(Key2, pcl_fetch(PCLr, {o,"Bucket0002", "Key0002"})),
|
?assertMatch(Key2, pcl_fetch(PCLr, {o,"Bucket0002", "Key0002", null})),
|
||||||
?assertMatch(Key3, pcl_fetch(PCLr, {o,"Bucket0003", "Key0003"})),
|
?assertMatch(Key3, pcl_fetch(PCLr, {o,"Bucket0003", "Key0003", null})),
|
||||||
maybe_pause_push(pcl_pushmem(PCLr, KL3)),
|
maybe_pause_push(pcl_pushmem(PCLr, KL3)),
|
||||||
maybe_pause_push(pcl_pushmem(PCLr, [Key4])),
|
maybe_pause_push(pcl_pushmem(PCLr, [Key4])),
|
||||||
maybe_pause_push(pcl_pushmem(PCLr, KL4)),
|
maybe_pause_push(pcl_pushmem(PCLr, KL4)),
|
||||||
?assertMatch(Key1, pcl_fetch(PCLr, {o,"Bucket0001", "Key0001"})),
|
?assertMatch(Key1, pcl_fetch(PCLr, {o,"Bucket0001", "Key0001", null})),
|
||||||
?assertMatch(Key2, pcl_fetch(PCLr, {o,"Bucket0002", "Key0002"})),
|
?assertMatch(Key2, pcl_fetch(PCLr, {o,"Bucket0002", "Key0002", null})),
|
||||||
?assertMatch(Key3, pcl_fetch(PCLr, {o,"Bucket0003", "Key0003"})),
|
?assertMatch(Key3, pcl_fetch(PCLr, {o,"Bucket0003", "Key0003", null})),
|
||||||
?assertMatch(Key4, pcl_fetch(PCLr, {o,"Bucket0004", "Key0004"})),
|
?assertMatch(Key4, pcl_fetch(PCLr, {o,"Bucket0004", "Key0004", null})),
|
||||||
SnapOpts = #penciller_options{start_snapshot = true,
|
SnapOpts = #penciller_options{start_snapshot = true,
|
||||||
source_penciller = PCLr},
|
source_penciller = PCLr},
|
||||||
{ok, PclSnap} = pcl_start(SnapOpts),
|
{ok, PclSnap} = pcl_start(SnapOpts),
|
||||||
ok = pcl_loadsnapshot(PclSnap, []),
|
ok = pcl_loadsnapshot(PclSnap, []),
|
||||||
?assertMatch(Key1, pcl_fetch(PclSnap, {o,"Bucket0001", "Key0001"})),
|
?assertMatch(Key1, pcl_fetch(PclSnap, {o,"Bucket0001", "Key0001", null})),
|
||||||
?assertMatch(Key2, pcl_fetch(PclSnap, {o,"Bucket0002", "Key0002"})),
|
?assertMatch(Key2, pcl_fetch(PclSnap, {o,"Bucket0002", "Key0002", null})),
|
||||||
?assertMatch(Key3, pcl_fetch(PclSnap, {o,"Bucket0003", "Key0003"})),
|
?assertMatch(Key3, pcl_fetch(PclSnap, {o,"Bucket0003", "Key0003", null})),
|
||||||
?assertMatch(Key4, pcl_fetch(PclSnap, {o,"Bucket0004", "Key0004"})),
|
?assertMatch(Key4, pcl_fetch(PclSnap, {o,"Bucket0004", "Key0004", null})),
|
||||||
?assertMatch(true, pcl_checksequencenumber(PclSnap,
|
?assertMatch(true, pcl_checksequencenumber(PclSnap,
|
||||||
{o,"Bucket0001", "Key0001"},
|
{o,
|
||||||
|
"Bucket0001",
|
||||||
|
"Key0001",
|
||||||
|
null},
|
||||||
1)),
|
1)),
|
||||||
?assertMatch(true, pcl_checksequencenumber(PclSnap,
|
?assertMatch(true, pcl_checksequencenumber(PclSnap,
|
||||||
{o,"Bucket0002", "Key0002"},
|
{o,
|
||||||
|
"Bucket0002",
|
||||||
|
"Key0002",
|
||||||
|
null},
|
||||||
1002)),
|
1002)),
|
||||||
?assertMatch(true, pcl_checksequencenumber(PclSnap,
|
?assertMatch(true, pcl_checksequencenumber(PclSnap,
|
||||||
{o,"Bucket0003", "Key0003"},
|
{o,
|
||||||
|
"Bucket0003",
|
||||||
|
"Key0003",
|
||||||
|
null},
|
||||||
2002)),
|
2002)),
|
||||||
?assertMatch(true, pcl_checksequencenumber(PclSnap,
|
?assertMatch(true, pcl_checksequencenumber(PclSnap,
|
||||||
{o,"Bucket0004", "Key0004"},
|
{o,
|
||||||
|
"Bucket0004",
|
||||||
|
"Key0004",
|
||||||
|
null},
|
||||||
3002)),
|
3002)),
|
||||||
% Add some more keys and confirm that chekc sequence number still
|
% Add some more keys and confirm that chekc sequence number still
|
||||||
% sees the old version in the previous snapshot, but will see the new version
|
% sees the old version in the previous snapshot, but will see the new version
|
||||||
% in a new snapshot
|
% in a new snapshot
|
||||||
Key1A = {{o,"Bucket0001", "Key0001"}, {4002, {active, infinity}, null}},
|
Key1A = {{o,"Bucket0001", "Key0001", null}, {4002, {active, infinity}, null}},
|
||||||
KL1A = lists:sort(leveled_sft:generate_randomkeys({4002, 2})),
|
KL1A = lists:sort(leveled_sft:generate_randomkeys({4002, 2})),
|
||||||
maybe_pause_push(pcl_pushmem(PCLr, [Key1A])),
|
maybe_pause_push(pcl_pushmem(PCLr, [Key1A])),
|
||||||
maybe_pause_push(pcl_pushmem(PCLr, KL1A)),
|
maybe_pause_push(pcl_pushmem(PCLr, KL1A)),
|
||||||
?assertMatch(true, pcl_checksequencenumber(PclSnap,
|
?assertMatch(true, pcl_checksequencenumber(PclSnap,
|
||||||
{o,"Bucket0001", "Key0001"},
|
{o,
|
||||||
|
"Bucket0001",
|
||||||
|
"Key0001",
|
||||||
|
null},
|
||||||
1)),
|
1)),
|
||||||
ok = pcl_close(PclSnap),
|
ok = pcl_close(PclSnap),
|
||||||
{ok, PclSnap2} = pcl_start(SnapOpts),
|
{ok, PclSnap2} = pcl_start(SnapOpts),
|
||||||
ok = pcl_loadsnapshot(PclSnap2, []),
|
ok = pcl_loadsnapshot(PclSnap2, []),
|
||||||
?assertMatch(false, pcl_checksequencenumber(PclSnap2,
|
?assertMatch(false, pcl_checksequencenumber(PclSnap2,
|
||||||
{o,"Bucket0001", "Key0001"},
|
{o,
|
||||||
|
"Bucket0001",
|
||||||
|
"Key0001",
|
||||||
|
null},
|
||||||
1)),
|
1)),
|
||||||
?assertMatch(true, pcl_checksequencenumber(PclSnap2,
|
?assertMatch(true, pcl_checksequencenumber(PclSnap2,
|
||||||
{o,"Bucket0001", "Key0001"},
|
{o,
|
||||||
|
"Bucket0001",
|
||||||
|
"Key0001",
|
||||||
|
null},
|
||||||
4002)),
|
4002)),
|
||||||
?assertMatch(true, pcl_checksequencenumber(PclSnap2,
|
?assertMatch(true, pcl_checksequencenumber(PclSnap2,
|
||||||
{o,"Bucket0002", "Key0002"},
|
{o,
|
||||||
|
"Bucket0002",
|
||||||
|
"Key0002",
|
||||||
|
null},
|
||||||
1002)),
|
1002)),
|
||||||
ok = pcl_close(PclSnap2),
|
ok = pcl_close(PclSnap2),
|
||||||
ok = pcl_close(PCLr),
|
ok = pcl_close(PCLr),
|
||||||
|
@ -1334,4 +1597,174 @@ memcopy_updatecache_test() ->
|
||||||
?assertMatch(2000, Size2),
|
?assertMatch(2000, Size2),
|
||||||
?assertMatch(3000, MemCopy4#l0snapshot.ledger_sqn).
|
?assertMatch(3000, MemCopy4#l0snapshot.ledger_sqn).
|
||||||
|
|
||||||
|
rangequery_manifest_test() ->
|
||||||
|
{E1,
|
||||||
|
E2,
|
||||||
|
E3} = {#manifest_entry{start_key={i, "Bucket1", {"Idx1", "Fld1"}, "K8"},
|
||||||
|
end_key={i, "Bucket1", {"Idx1", "Fld9"}, "K93"},
|
||||||
|
filename="Z1"},
|
||||||
|
#manifest_entry{start_key={i, "Bucket1", {"Idx1", "Fld9"}, "K97"},
|
||||||
|
end_key={o, "Bucket1", "K71", null},
|
||||||
|
filename="Z2"},
|
||||||
|
#manifest_entry{start_key={o, "Bucket1", "K75", null},
|
||||||
|
end_key={o, "Bucket1", "K993", null},
|
||||||
|
filename="Z3"}},
|
||||||
|
{E4,
|
||||||
|
E5,
|
||||||
|
E6} = {#manifest_entry{start_key={i, "Bucket1", {"Idx1", "Fld1"}, "K8"},
|
||||||
|
end_key={i, "Bucket1", {"Idx1", "Fld7"}, "K93"},
|
||||||
|
filename="Z4"},
|
||||||
|
#manifest_entry{start_key={i, "Bucket1", {"Idx1", "Fld7"}, "K97"},
|
||||||
|
end_key={o, "Bucket1", "K78", null},
|
||||||
|
filename="Z5"},
|
||||||
|
#manifest_entry{start_key={o, "Bucket1", "K81", null},
|
||||||
|
end_key={o, "Bucket1", "K996", null},
|
||||||
|
filename="Z6"}},
|
||||||
|
Man = [{1, [E1, E2, E3]}, {2, [E4, E5, E6]}],
|
||||||
|
R1 = initiate_rangequery_frommanifest({o, "Bucket1", "K711", null},
|
||||||
|
{o, "Bucket1", "K999", null},
|
||||||
|
Man),
|
||||||
|
?assertMatch([{1, [{next_file, E3}]},
|
||||||
|
{2, [{next_file, E5}, {next_file, E6}]}],
|
||||||
|
R1),
|
||||||
|
R2 = initiate_rangequery_frommanifest({i, "Bucket1", {"Idx1", "Fld8"}, null},
|
||||||
|
{i, "Bucket1", {"Idx1", "Fld8"}, null},
|
||||||
|
Man),
|
||||||
|
?assertMatch([{1, [{next_file, E1}]}, {2, [{next_file, E5}]}],
|
||||||
|
R2),
|
||||||
|
R3 = initiate_rangequery_frommanifest({i, "Bucket1", {"Idx0", "Fld8"}, null},
|
||||||
|
{i, "Bucket1", {"Idx0", "Fld9"}, null},
|
||||||
|
Man),
|
||||||
|
?assertMatch([], R3).
|
||||||
|
|
||||||
|
simple_findnextkey_test() ->
|
||||||
|
QueryArray = [
|
||||||
|
{2, [{{o, "Bucket1", "Key1"}, {5, {active, infinity}, null}},
|
||||||
|
{{o, "Bucket1", "Key5"}, {4, {active, infinity}, null}}]},
|
||||||
|
{3, [{{o, "Bucket1", "Key3"}, {3, {active, infinity}, null}}]},
|
||||||
|
{5, [{{o, "Bucket1", "Key2"}, {2, {active, infinity}, null}}]}
|
||||||
|
],
|
||||||
|
{Array2, KV1} = find_nextkey(QueryArray,
|
||||||
|
{o, "Bucket1", "Key0"},
|
||||||
|
{o, "Bucket1", "Key5"}),
|
||||||
|
?assertMatch({{o, "Bucket1", "Key1"}, {5, {active, infinity}, null}}, KV1),
|
||||||
|
{Array3, KV2} = find_nextkey(Array2,
|
||||||
|
{o, "Bucket1", "Key0"},
|
||||||
|
{o, "Bucket1", "Key5"}),
|
||||||
|
?assertMatch({{o, "Bucket1", "Key2"}, {2, {active, infinity}, null}}, KV2),
|
||||||
|
{Array4, KV3} = find_nextkey(Array3,
|
||||||
|
{o, "Bucket1", "Key0"},
|
||||||
|
{o, "Bucket1", "Key5"}),
|
||||||
|
?assertMatch({{o, "Bucket1", "Key3"}, {3, {active, infinity}, null}}, KV3),
|
||||||
|
{Array5, KV4} = find_nextkey(Array4,
|
||||||
|
{o, "Bucket1", "Key0"},
|
||||||
|
{o, "Bucket1", "Key5"}),
|
||||||
|
?assertMatch({{o, "Bucket1", "Key5"}, {4, {active, infinity}, null}}, KV4),
|
||||||
|
ER = find_nextkey(Array5,
|
||||||
|
{o, "Bucket1", "Key0"},
|
||||||
|
{o, "Bucket1", "Key5"}),
|
||||||
|
?assertMatch(no_more_keys, ER).
|
||||||
|
|
||||||
|
sqnoverlap_findnextkey_test() ->
|
||||||
|
QueryArray = [
|
||||||
|
{2, [{{o, "Bucket1", "Key1"}, {5, {active, infinity}, null}},
|
||||||
|
{{o, "Bucket1", "Key5"}, {4, {active, infinity}, null}}]},
|
||||||
|
{3, [{{o, "Bucket1", "Key3"}, {3, {active, infinity}, null}}]},
|
||||||
|
{5, [{{o, "Bucket1", "Key5"}, {2, {active, infinity}, null}}]}
|
||||||
|
],
|
||||||
|
{Array2, KV1} = find_nextkey(QueryArray,
|
||||||
|
{o, "Bucket1", "Key0"},
|
||||||
|
{o, "Bucket1", "Key5"}),
|
||||||
|
?assertMatch({{o, "Bucket1", "Key1"}, {5, {active, infinity}, null}}, KV1),
|
||||||
|
{Array3, KV2} = find_nextkey(Array2,
|
||||||
|
{o, "Bucket1", "Key0"},
|
||||||
|
{o, "Bucket1", "Key5"}),
|
||||||
|
?assertMatch({{o, "Bucket1", "Key3"}, {3, {active, infinity}, null}}, KV2),
|
||||||
|
{Array4, KV3} = find_nextkey(Array3,
|
||||||
|
{o, "Bucket1", "Key0"},
|
||||||
|
{o, "Bucket1", "Key5"}),
|
||||||
|
?assertMatch({{o, "Bucket1", "Key5"}, {4, {active, infinity}, null}}, KV3),
|
||||||
|
ER = find_nextkey(Array4,
|
||||||
|
{o, "Bucket1", "Key0"},
|
||||||
|
{o, "Bucket1", "Key5"}),
|
||||||
|
?assertMatch(no_more_keys, ER).
|
||||||
|
|
||||||
|
sqnoverlap_otherway_findnextkey_test() ->
|
||||||
|
QueryArray = [
|
||||||
|
{2, [{{o, "Bucket1", "Key1"}, {5, {active, infinity}, null}},
|
||||||
|
{{o, "Bucket1", "Key5"}, {1, {active, infinity}, null}}]},
|
||||||
|
{3, [{{o, "Bucket1", "Key3"}, {3, {active, infinity}, null}}]},
|
||||||
|
{5, [{{o, "Bucket1", "Key5"}, {2, {active, infinity}, null}}]}
|
||||||
|
],
|
||||||
|
{Array2, KV1} = find_nextkey(QueryArray,
|
||||||
|
{o, "Bucket1", "Key0"},
|
||||||
|
{o, "Bucket1", "Key5"}),
|
||||||
|
?assertMatch({{o, "Bucket1", "Key1"}, {5, {active, infinity}, null}}, KV1),
|
||||||
|
{Array3, KV2} = find_nextkey(Array2,
|
||||||
|
{o, "Bucket1", "Key0"},
|
||||||
|
{o, "Bucket1", "Key5"}),
|
||||||
|
?assertMatch({{o, "Bucket1", "Key3"}, {3, {active, infinity}, null}}, KV2),
|
||||||
|
{Array4, KV3} = find_nextkey(Array3,
|
||||||
|
{o, "Bucket1", "Key0"},
|
||||||
|
{o, "Bucket1", "Key5"}),
|
||||||
|
?assertMatch({{o, "Bucket1", "Key5"}, {2, {active, infinity}, null}}, KV3),
|
||||||
|
ER = find_nextkey(Array4,
|
||||||
|
{o, "Bucket1", "Key0"},
|
||||||
|
{o, "Bucket1", "Key5"}),
|
||||||
|
?assertMatch(no_more_keys, ER).
|
||||||
|
|
||||||
|
foldwithimm_simple_test() ->
|
||||||
|
QueryArray = [
|
||||||
|
{2, [{{o, "Bucket1", "Key1"}, {5, {active, infinity}, null}},
|
||||||
|
{{o, "Bucket1", "Key5"}, {1, {active, infinity}, null}}]},
|
||||||
|
{3, [{{o, "Bucket1", "Key3"}, {3, {active, infinity}, null}}]},
|
||||||
|
{5, [{{o, "Bucket1", "Key5"}, {2, {active, infinity}, null}}]}
|
||||||
|
],
|
||||||
|
IMM0 = gb_trees:enter({o, "Bucket1", "Key6"},
|
||||||
|
{7, {active, infinity}, null},
|
||||||
|
gb_trees:empty()),
|
||||||
|
IMM1 = gb_trees:enter({o, "Bucket1", "Key1"},
|
||||||
|
{8, {active, infinity}, null},
|
||||||
|
IMM0),
|
||||||
|
IMM2 = gb_trees:enter({o, "Bucket1", "Key8"},
|
||||||
|
{9, {active, infinity}, null},
|
||||||
|
IMM1),
|
||||||
|
IMMiter = gb_trees:iterator_from({o, "Bucket1", "Key1"}, IMM2),
|
||||||
|
AccFun = fun(K, V, Acc) -> SQN= leveled_bookie:strip_to_seqonly({K, V}),
|
||||||
|
Acc ++ [{K, SQN}] end,
|
||||||
|
Acc = keyfolder(IMMiter,
|
||||||
|
QueryArray,
|
||||||
|
{o, "Bucket1", "Key1"}, {o, "Bucket1", "Key6"},
|
||||||
|
{AccFun, []}),
|
||||||
|
?assertMatch([{{o, "Bucket1", "Key1"}, 8},
|
||||||
|
{{o, "Bucket1", "Key3"}, 3},
|
||||||
|
{{o, "Bucket1", "Key5"}, 2},
|
||||||
|
{{o, "Bucket1", "Key6"}, 7}], Acc),
|
||||||
|
|
||||||
|
IMM1A = gb_trees:enter({o, "Bucket1", "Key1"},
|
||||||
|
{8, {active, infinity}, null},
|
||||||
|
gb_trees:empty()),
|
||||||
|
IMMiterA = gb_trees:iterator_from({o, "Bucket1", "Key1"}, IMM1A),
|
||||||
|
AccA = keyfolder(IMMiterA,
|
||||||
|
QueryArray,
|
||||||
|
{o, "Bucket1", "Key1"}, {o, "Bucket1", "Key6"},
|
||||||
|
{AccFun, []}),
|
||||||
|
?assertMatch([{{o, "Bucket1", "Key1"}, 8},
|
||||||
|
{{o, "Bucket1", "Key3"}, 3},
|
||||||
|
{{o, "Bucket1", "Key5"}, 2}], AccA),
|
||||||
|
|
||||||
|
IMM3 = gb_trees:enter({o, "Bucket1", "Key4"},
|
||||||
|
{10, {active, infinity}, null},
|
||||||
|
IMM2),
|
||||||
|
IMMiterB = gb_trees:iterator_from({o, "Bucket1", "Key1"}, IMM3),
|
||||||
|
AccB = keyfolder(IMMiterB,
|
||||||
|
QueryArray,
|
||||||
|
{o, "Bucket1", "Key1"}, {o, "Bucket1", "Key6"},
|
||||||
|
{AccFun, []}),
|
||||||
|
?assertMatch([{{o, "Bucket1", "Key1"}, 8},
|
||||||
|
{{o, "Bucket1", "Key3"}, 3},
|
||||||
|
{{o, "Bucket1", "Key4"}, 10},
|
||||||
|
{{o, "Bucket1", "Key5"}, 2},
|
||||||
|
{{o, "Bucket1", "Key6"}, 7}], AccB).
|
||||||
|
|
||||||
-endif.
|
-endif.
|
|
@ -14,8 +14,8 @@
|
||||||
%%
|
%%
|
||||||
%% All keys are not equal in sft files, keys are only expected in a specific
|
%% All keys are not equal in sft files, keys are only expected in a specific
|
||||||
%% series of formats
|
%% series of formats
|
||||||
%% - {o, Bucket, Key} - Object Keys
|
%% - {o, Bucket, Key, SubKey|null} - Object Keys
|
||||||
%% - {i, Bucket, IndexName, IndexTerm, Key} - Postings
|
%% - {i, Bucket, {IndexName, IndexTerm}, Key} - Postings
|
||||||
%% The {Bucket, Key} part of all types of keys are hashed for segment filters.
|
%% The {Bucket, Key} part of all types of keys are hashed for segment filters.
|
||||||
%% For Postings the {Bucket, IndexName, IndexTerm} is also hashed. This
|
%% For Postings the {Bucket, IndexName, IndexTerm} is also hashed. This
|
||||||
%% causes a false positive on lookup of a segment, but allows for the presence
|
%% causes a false positive on lookup of a segment, but allows for the presence
|
||||||
|
@ -155,7 +155,7 @@
|
||||||
sft_new/5,
|
sft_new/5,
|
||||||
sft_open/1,
|
sft_open/1,
|
||||||
sft_get/2,
|
sft_get/2,
|
||||||
sft_getkeyrange/4,
|
sft_getkvrange/4,
|
||||||
sft_close/1,
|
sft_close/1,
|
||||||
sft_clear/1,
|
sft_clear/1,
|
||||||
sft_checkready/1,
|
sft_checkready/1,
|
||||||
|
@ -243,15 +243,8 @@ sft_setfordelete(Pid, Penciller) ->
|
||||||
sft_get(Pid, Key) ->
|
sft_get(Pid, Key) ->
|
||||||
gen_server:call(Pid, {get_kv, Key}, infinity).
|
gen_server:call(Pid, {get_kv, Key}, infinity).
|
||||||
|
|
||||||
sft_getkeyrange(Pid, StartKey, EndKey, ScanWidth) ->
|
|
||||||
gen_server:call(Pid,
|
|
||||||
{get_keyrange, StartKey, EndKey, ScanWidth},
|
|
||||||
infinity).
|
|
||||||
|
|
||||||
sft_getkvrange(Pid, StartKey, EndKey, ScanWidth) ->
|
sft_getkvrange(Pid, StartKey, EndKey, ScanWidth) ->
|
||||||
gen_server:call(Pid,
|
gen_server:call(Pid, {get_kvrange, StartKey, EndKey, ScanWidth}, infinity).
|
||||||
{get_kvrange, StartKey, EndKey, ScanWidth},
|
|
||||||
infinity).
|
|
||||||
|
|
||||||
sft_clear(Pid) ->
|
sft_clear(Pid) ->
|
||||||
gen_server:call(Pid, clear, infinity).
|
gen_server:call(Pid, clear, infinity).
|
||||||
|
@ -313,15 +306,13 @@ handle_call({sft_open, Filename}, _From, _State) ->
|
||||||
handle_call({get_kv, Key}, _From, State) ->
|
handle_call({get_kv, Key}, _From, State) ->
|
||||||
Reply = fetch_keyvalue(State#state.handle, State, Key),
|
Reply = fetch_keyvalue(State#state.handle, State, Key),
|
||||||
{reply, Reply, State};
|
{reply, Reply, State};
|
||||||
handle_call({get_keyrange, StartKey, EndKey, ScanWidth}, _From, State) ->
|
|
||||||
Reply = fetch_range_keysonly(State#state.handle, State,
|
|
||||||
StartKey, EndKey,
|
|
||||||
ScanWidth),
|
|
||||||
{reply, Reply, State};
|
|
||||||
handle_call({get_kvrange, StartKey, EndKey, ScanWidth}, _From, State) ->
|
handle_call({get_kvrange, StartKey, EndKey, ScanWidth}, _From, State) ->
|
||||||
Reply = fetch_range_kv(State#state.handle, State,
|
Reply = pointer_append_queryresults(fetch_range_kv(State#state.handle,
|
||||||
StartKey, EndKey,
|
State,
|
||||||
ScanWidth),
|
StartKey,
|
||||||
|
EndKey,
|
||||||
|
ScanWidth),
|
||||||
|
self()),
|
||||||
{reply, Reply, State};
|
{reply, Reply, State};
|
||||||
handle_call(close, _From, State) ->
|
handle_call(close, _From, State) ->
|
||||||
{stop, normal, ok, State};
|
{stop, normal, ok, State};
|
||||||
|
@ -582,7 +573,7 @@ acc_list_keysonly(null, empty) ->
|
||||||
acc_list_keysonly(null, RList) ->
|
acc_list_keysonly(null, RList) ->
|
||||||
RList;
|
RList;
|
||||||
acc_list_keysonly(R, RList) ->
|
acc_list_keysonly(R, RList) ->
|
||||||
lists:append(RList, [leveled_bookie:strip_to_keyseqonly(R)]).
|
lists:append(RList, [leveled_bookie:strip_to_keyseqstatusonly(R)]).
|
||||||
|
|
||||||
acc_list_kv(null, empty) ->
|
acc_list_kv(null, empty) ->
|
||||||
[];
|
[];
|
||||||
|
@ -672,10 +663,12 @@ scan_block([], StartKey, _EndKey, _FunList, _AccFun, Acc) ->
|
||||||
{partial, Acc, StartKey};
|
{partial, Acc, StartKey};
|
||||||
scan_block([HeadKV|T], StartKey, EndKey, FunList, AccFun, Acc) ->
|
scan_block([HeadKV|T], StartKey, EndKey, FunList, AccFun, Acc) ->
|
||||||
K = leveled_bookie:strip_to_keyonly(HeadKV),
|
K = leveled_bookie:strip_to_keyonly(HeadKV),
|
||||||
case K of
|
Pre = leveled_bookie:key_compare(StartKey, K, gt),
|
||||||
K when K < StartKey, StartKey /= all ->
|
Post = leveled_bookie:key_compare(EndKey, K, lt),
|
||||||
|
case {Pre, Post} of
|
||||||
|
{true, _} when StartKey /= all ->
|
||||||
scan_block(T, StartKey, EndKey, FunList, AccFun, Acc);
|
scan_block(T, StartKey, EndKey, FunList, AccFun, Acc);
|
||||||
K when K > EndKey, EndKey /= all ->
|
{_, true} when EndKey /= all ->
|
||||||
{complete, Acc};
|
{complete, Acc};
|
||||||
_ ->
|
_ ->
|
||||||
case applyfuns(FunList, HeadKV) of
|
case applyfuns(FunList, HeadKV) of
|
||||||
|
@ -1121,8 +1114,7 @@ maybe_expand_pointer([H|Tail]) ->
|
||||||
case H of
|
case H of
|
||||||
{next, SFTPid, StartKey} ->
|
{next, SFTPid, StartKey} ->
|
||||||
%% io:format("Scanning further on PID ~w ~w~n", [SFTPid, StartKey]),
|
%% io:format("Scanning further on PID ~w ~w~n", [SFTPid, StartKey]),
|
||||||
QResult = sft_getkvrange(SFTPid, StartKey, all, ?MERGE_SCANWIDTH),
|
Acc = sft_getkvrange(SFTPid, StartKey, all, ?MERGE_SCANWIDTH),
|
||||||
Acc = pointer_append_queryresults(QResult, SFTPid),
|
|
||||||
lists:append(Acc, Tail);
|
lists:append(Acc, Tail);
|
||||||
_ ->
|
_ ->
|
||||||
[H|Tail]
|
[H|Tail]
|
||||||
|
@ -1409,8 +1401,9 @@ generate_randomkeys(0, _SQN, Acc) ->
|
||||||
Acc;
|
Acc;
|
||||||
generate_randomkeys(Count, SQN, Acc) ->
|
generate_randomkeys(Count, SQN, Acc) ->
|
||||||
RandKey = {{o,
|
RandKey = {{o,
|
||||||
lists:concat(["Bucket", random:uniform(1024)]),
|
lists:concat(["Bucket", random:uniform(1024)]),
|
||||||
lists:concat(["Key", random:uniform(1024)])},
|
lists:concat(["Key", random:uniform(1024)]),
|
||||||
|
null},
|
||||||
{SQN,
|
{SQN,
|
||||||
{active, infinity}, null}},
|
{active, infinity}, null}},
|
||||||
generate_randomkeys(Count - 1, SQN + 1, [RandKey|Acc]).
|
generate_randomkeys(Count - 1, SQN + 1, [RandKey|Acc]).
|
||||||
|
@ -1423,73 +1416,74 @@ generate_sequentialkeys(Target, Incr, Acc) when Incr =:= Target ->
|
||||||
generate_sequentialkeys(Target, Incr, Acc) ->
|
generate_sequentialkeys(Target, Incr, Acc) ->
|
||||||
KeyStr = string:right(integer_to_list(Incr), 8, $0),
|
KeyStr = string:right(integer_to_list(Incr), 8, $0),
|
||||||
NextKey = {{o,
|
NextKey = {{o,
|
||||||
"BucketSeq",
|
"BucketSeq",
|
||||||
lists:concat(["Key", KeyStr])},
|
lists:concat(["Key", KeyStr]),
|
||||||
|
null},
|
||||||
{5,
|
{5,
|
||||||
{active, infinity}, null}},
|
{active, infinity}, null}},
|
||||||
generate_sequentialkeys(Target, Incr + 1, [NextKey|Acc]).
|
generate_sequentialkeys(Target, Incr + 1, [NextKey|Acc]).
|
||||||
|
|
||||||
simple_create_block_test() ->
|
simple_create_block_test() ->
|
||||||
KeyList1 = [{{o, "Bucket1", "Key1"}, {1, {active, infinity}, null}},
|
KeyList1 = [{{o, "Bucket1", "Key1", null}, {1, {active, infinity}, null}},
|
||||||
{{o, "Bucket1", "Key3"}, {2, {active, infinity}, null}}],
|
{{o, "Bucket1", "Key3", null}, {2, {active, infinity}, null}}],
|
||||||
KeyList2 = [{{o, "Bucket1", "Key2"}, {3, {active, infinity}, null}}],
|
KeyList2 = [{{o, "Bucket1", "Key2", null}, {3, {active, infinity}, null}}],
|
||||||
{MergedKeyList, ListStatus, SN, _, _, _} = create_block(KeyList1,
|
{MergedKeyList, ListStatus, SN, _, _, _} = create_block(KeyList1,
|
||||||
KeyList2,
|
KeyList2,
|
||||||
1),
|
1),
|
||||||
?assertMatch(partial, ListStatus),
|
?assertMatch(partial, ListStatus),
|
||||||
[H1|T1] = MergedKeyList,
|
[H1|T1] = MergedKeyList,
|
||||||
?assertMatch(H1, {{o, "Bucket1", "Key1"}, {1, {active, infinity}, null}}),
|
?assertMatch(H1, {{o, "Bucket1", "Key1", null}, {1, {active, infinity}, null}}),
|
||||||
[H2|T2] = T1,
|
[H2|T2] = T1,
|
||||||
?assertMatch(H2, {{o, "Bucket1", "Key2"}, {3, {active, infinity}, null}}),
|
?assertMatch(H2, {{o, "Bucket1", "Key2", null}, {3, {active, infinity}, null}}),
|
||||||
?assertMatch(T2, [{{o, "Bucket1", "Key3"}, {2, {active, infinity}, null}}]),
|
?assertMatch(T2, [{{o, "Bucket1", "Key3", null}, {2, {active, infinity}, null}}]),
|
||||||
?assertMatch(SN, {1,3}).
|
?assertMatch(SN, {1,3}).
|
||||||
|
|
||||||
dominate_create_block_test() ->
|
dominate_create_block_test() ->
|
||||||
KeyList1 = [{{o, "Bucket1", "Key1"}, {1, {active, infinity}, null}},
|
KeyList1 = [{{o, "Bucket1", "Key1", null}, {1, {active, infinity}, null}},
|
||||||
{{o, "Bucket1", "Key2"}, {2, {active, infinity}, null}}],
|
{{o, "Bucket1", "Key2", null}, {2, {active, infinity}, null}}],
|
||||||
KeyList2 = [{{o, "Bucket1", "Key2"}, {3, {tomb, infinity}, null}}],
|
KeyList2 = [{{o, "Bucket1", "Key2", null}, {3, {tomb, infinity}, null}}],
|
||||||
{MergedKeyList, ListStatus, SN, _, _, _} = create_block(KeyList1,
|
{MergedKeyList, ListStatus, SN, _, _, _} = create_block(KeyList1,
|
||||||
KeyList2,
|
KeyList2,
|
||||||
1),
|
1),
|
||||||
?assertMatch(partial, ListStatus),
|
?assertMatch(partial, ListStatus),
|
||||||
[K1, K2] = MergedKeyList,
|
[K1, K2] = MergedKeyList,
|
||||||
?assertMatch(K1, {{o, "Bucket1", "Key1"}, {1, {active, infinity}, null}}),
|
?assertMatch(K1, {{o, "Bucket1", "Key1", null}, {1, {active, infinity}, null}}),
|
||||||
?assertMatch(K2, {{o, "Bucket1", "Key2"}, {3, {tomb, infinity}, null}}),
|
?assertMatch(K2, {{o, "Bucket1", "Key2", null}, {3, {tomb, infinity}, null}}),
|
||||||
?assertMatch(SN, {1,3}).
|
?assertMatch(SN, {1,3}).
|
||||||
|
|
||||||
sample_keylist() ->
|
sample_keylist() ->
|
||||||
KeyList1 = [{{o, "Bucket1", "Key1"}, {1, {active, infinity}, null}},
|
KeyList1 = [{{o, "Bucket1", "Key1", null}, {1, {active, infinity}, null}},
|
||||||
{{o, "Bucket1", "Key3"}, {1, {active, infinity}, null}},
|
{{o, "Bucket1", "Key3", null}, {1, {active, infinity}, null}},
|
||||||
{{o, "Bucket1", "Key5"}, {1, {active, infinity}, null}},
|
{{o, "Bucket1", "Key5", null}, {1, {active, infinity}, null}},
|
||||||
{{o, "Bucket1", "Key7"}, {1, {active, infinity}, null}},
|
{{o, "Bucket1", "Key7", null}, {1, {active, infinity}, null}},
|
||||||
{{o, "Bucket1", "Key9"}, {1, {active, infinity}, null}},
|
{{o, "Bucket1", "Key9", null}, {1, {active, infinity}, null}},
|
||||||
{{o, "Bucket2", "Key1"}, {1, {active, infinity}, null}},
|
{{o, "Bucket2", "Key1", null}, {1, {active, infinity}, null}},
|
||||||
{{o, "Bucket2", "Key3"}, {1, {active, infinity}, null}},
|
{{o, "Bucket2", "Key3", null}, {1, {active, infinity}, null}},
|
||||||
{{o, "Bucket2", "Key5"}, {1, {active, infinity}, null}},
|
{{o, "Bucket2", "Key5", null}, {1, {active, infinity}, null}},
|
||||||
{{o, "Bucket2", "Key7"}, {1, {active, infinity}, null}},
|
{{o, "Bucket2", "Key7", null}, {1, {active, infinity}, null}},
|
||||||
{{o, "Bucket2", "Key9"}, {1, {active, infinity}, null}},
|
{{o, "Bucket2", "Key9", null}, {1, {active, infinity}, null}},
|
||||||
{{o, "Bucket3", "Key1"}, {1, {active, infinity}, null}},
|
{{o, "Bucket3", "Key1", null}, {1, {active, infinity}, null}},
|
||||||
{{o, "Bucket3", "Key3"}, {1, {active, infinity}, null}},
|
{{o, "Bucket3", "Key3", null}, {1, {active, infinity}, null}},
|
||||||
{{o, "Bucket3", "Key5"}, {1, {active, infinity}, null}},
|
{{o, "Bucket3", "Key5", null}, {1, {active, infinity}, null}},
|
||||||
{{o, "Bucket3", "Key7"}, {1, {active, infinity}, null}},
|
{{o, "Bucket3", "Key7", null}, {1, {active, infinity}, null}},
|
||||||
{{o, "Bucket3", "Key9"}, {1, {active, infinity}, null}},
|
{{o, "Bucket3", "Key9", null}, {1, {active, infinity}, null}},
|
||||||
{{o, "Bucket4", "Key1"}, {1, {active, infinity}, null}}],
|
{{o, "Bucket4", "Key1", null}, {1, {active, infinity}, null}}],
|
||||||
KeyList2 = [{{o, "Bucket1", "Key2"}, {1, {active, infinity}, null}},
|
KeyList2 = [{{o, "Bucket1", "Key2", null}, {1, {active, infinity}, null}},
|
||||||
{{o, "Bucket1", "Key4"}, {1, {active, infinity}, null}},
|
{{o, "Bucket1", "Key4", null}, {1, {active, infinity}, null}},
|
||||||
{{o, "Bucket1", "Key6"}, {1, {active, infinity}, null}},
|
{{o, "Bucket1", "Key6", null}, {1, {active, infinity}, null}},
|
||||||
{{o, "Bucket1", "Key8"}, {1, {active, infinity}, null}},
|
{{o, "Bucket1", "Key8", null}, {1, {active, infinity}, null}},
|
||||||
{{o, "Bucket1", "Key9a"}, {1, {active, infinity}, null}},
|
{{o, "Bucket1", "Key9a", null}, {1, {active, infinity}, null}},
|
||||||
{{o, "Bucket1", "Key9b"}, {1, {active, infinity}, null}},
|
{{o, "Bucket1", "Key9b", null}, {1, {active, infinity}, null}},
|
||||||
{{o, "Bucket1", "Key9c"}, {1, {active, infinity}, null}},
|
{{o, "Bucket1", "Key9c", null}, {1, {active, infinity}, null}},
|
||||||
{{o, "Bucket1", "Key9d"}, {1, {active, infinity}, null}},
|
{{o, "Bucket1", "Key9d", null}, {1, {active, infinity}, null}},
|
||||||
{{o, "Bucket2", "Key2"}, {1, {active, infinity}, null}},
|
{{o, "Bucket2", "Key2", null}, {1, {active, infinity}, null}},
|
||||||
{{o, "Bucket2", "Key4"}, {1, {active, infinity}, null}},
|
{{o, "Bucket2", "Key4", null}, {1, {active, infinity}, null}},
|
||||||
{{o, "Bucket2", "Key6"}, {1, {active, infinity}, null}},
|
{{o, "Bucket2", "Key6", null}, {1, {active, infinity}, null}},
|
||||||
{{o, "Bucket2", "Key8"}, {1, {active, infinity}, null}},
|
{{o, "Bucket2", "Key8", null}, {1, {active, infinity}, null}},
|
||||||
{{o, "Bucket3", "Key2"}, {1, {active, infinity}, null}},
|
{{o, "Bucket3", "Key2", null}, {1, {active, infinity}, null}},
|
||||||
{{o, "Bucket3", "Key4"}, {3, {active, infinity}, null}},
|
{{o, "Bucket3", "Key4", null}, {3, {active, infinity}, null}},
|
||||||
{{o, "Bucket3", "Key6"}, {2, {active, infinity}, null}},
|
{{o, "Bucket3", "Key6", null}, {2, {active, infinity}, null}},
|
||||||
{{o, "Bucket3", "Key8"}, {1, {active, infinity}, null}}],
|
{{o, "Bucket3", "Key8", null}, {1, {active, infinity}, null}}],
|
||||||
{KeyList1, KeyList2}.
|
{KeyList1, KeyList2}.
|
||||||
|
|
||||||
alternating_create_block_test() ->
|
alternating_create_block_test() ->
|
||||||
|
@ -1501,12 +1495,12 @@ alternating_create_block_test() ->
|
||||||
?assertMatch(BlockSize, 32),
|
?assertMatch(BlockSize, 32),
|
||||||
?assertMatch(ListStatus, complete),
|
?assertMatch(ListStatus, complete),
|
||||||
K1 = lists:nth(1, MergedKeyList),
|
K1 = lists:nth(1, MergedKeyList),
|
||||||
?assertMatch(K1, {{o, "Bucket1", "Key1"}, {1, {active, infinity}, null}}),
|
?assertMatch(K1, {{o, "Bucket1", "Key1", null}, {1, {active, infinity}, null}}),
|
||||||
K11 = lists:nth(11, MergedKeyList),
|
K11 = lists:nth(11, MergedKeyList),
|
||||||
?assertMatch(K11, {{o, "Bucket1", "Key9b"}, {1, {active, infinity}, null}}),
|
?assertMatch(K11, {{o, "Bucket1", "Key9b", null}, {1, {active, infinity}, null}}),
|
||||||
K32 = lists:nth(32, MergedKeyList),
|
K32 = lists:nth(32, MergedKeyList),
|
||||||
?assertMatch(K32, {{o, "Bucket4", "Key1"}, {1, {active, infinity}, null}}),
|
?assertMatch(K32, {{o, "Bucket4", "Key1", null}, {1, {active, infinity}, null}}),
|
||||||
HKey = {{o, "Bucket1", "Key0"}, {1, {active, infinity}, null}},
|
HKey = {{o, "Bucket1", "Key0", null}, {1, {active, infinity}, null}},
|
||||||
{_, ListStatus2, _, _, _, _} = create_block([HKey|KeyList1], KeyList2, 1),
|
{_, ListStatus2, _, _, _, _} = create_block([HKey|KeyList1], KeyList2, 1),
|
||||||
?assertMatch(ListStatus2, full).
|
?assertMatch(ListStatus2, full).
|
||||||
|
|
||||||
|
@ -1565,17 +1559,17 @@ createslot_stage1_test() ->
|
||||||
{{LowKey, SegFilter, _SerialisedSlot, _LengthList},
|
{{LowKey, SegFilter, _SerialisedSlot, _LengthList},
|
||||||
{{LSN, HSN}, LastKey, Status},
|
{{LSN, HSN}, LastKey, Status},
|
||||||
KL1, KL2} = Out,
|
KL1, KL2} = Out,
|
||||||
?assertMatch(LowKey, {o, "Bucket1", "Key1"}),
|
?assertMatch(LowKey, {o, "Bucket1", "Key1", null}),
|
||||||
?assertMatch(LastKey, {o, "Bucket4", "Key1"}),
|
?assertMatch(LastKey, {o, "Bucket4", "Key1", null}),
|
||||||
?assertMatch(Status, partial),
|
?assertMatch(Status, partial),
|
||||||
?assertMatch(KL1, []),
|
?assertMatch(KL1, []),
|
||||||
?assertMatch(KL2, []),
|
?assertMatch(KL2, []),
|
||||||
R0 = check_for_segments(serialise_segment_filter(SegFilter),
|
R0 = check_for_segments(serialise_segment_filter(SegFilter),
|
||||||
[hash_for_segmentid({keyonly, {o, "Bucket1", "Key1"}})],
|
[hash_for_segmentid({keyonly, {o, "Bucket1", "Key1", null}})],
|
||||||
true),
|
true),
|
||||||
?assertMatch(R0, {maybe_present, [0]}),
|
?assertMatch(R0, {maybe_present, [0]}),
|
||||||
R1 = check_for_segments(serialise_segment_filter(SegFilter),
|
R1 = check_for_segments(serialise_segment_filter(SegFilter),
|
||||||
[hash_for_segmentid({keyonly, {o, "Bucket1", "Key99"}})],
|
[hash_for_segmentid({keyonly, {o, "Bucket1", "Key99", null}})],
|
||||||
true),
|
true),
|
||||||
?assertMatch(R1, not_present),
|
?assertMatch(R1, not_present),
|
||||||
?assertMatch(LSN, 1),
|
?assertMatch(LSN, 1),
|
||||||
|
@ -1605,25 +1599,29 @@ createslot_stage3_test() ->
|
||||||
Sum1 = lists:foldl(fun(X, Sum) -> Sum + X end, 0, LengthList),
|
Sum1 = lists:foldl(fun(X, Sum) -> Sum + X end, 0, LengthList),
|
||||||
Sum2 = byte_size(SerialisedSlot),
|
Sum2 = byte_size(SerialisedSlot),
|
||||||
?assertMatch(Sum1, Sum2),
|
?assertMatch(Sum1, Sum2),
|
||||||
?assertMatch(LowKey, {o, "BucketSeq", "Key00000001"}),
|
?assertMatch(LowKey, {o, "BucketSeq", "Key00000001", null}),
|
||||||
?assertMatch(LastKey, {o, "BucketSeq", "Key00000128"}),
|
?assertMatch(LastKey, {o, "BucketSeq", "Key00000128", null}),
|
||||||
?assertMatch(KL1, []),
|
?assertMatch(KL1, []),
|
||||||
Rem = length(KL2),
|
Rem = length(KL2),
|
||||||
?assertMatch(Rem, 72),
|
?assertMatch(Rem, 72),
|
||||||
R0 = check_for_segments(serialise_segment_filter(SegFilter),
|
R0 = check_for_segments(serialise_segment_filter(SegFilter),
|
||||||
[hash_for_segmentid({keyonly, {o, "BucketSeq", "Key00000100"}})],
|
[hash_for_segmentid({keyonly,
|
||||||
|
{o, "BucketSeq", "Key00000100", null}})],
|
||||||
true),
|
true),
|
||||||
?assertMatch(R0, {maybe_present, [3]}),
|
?assertMatch(R0, {maybe_present, [3]}),
|
||||||
R1 = check_for_segments(serialise_segment_filter(SegFilter),
|
R1 = check_for_segments(serialise_segment_filter(SegFilter),
|
||||||
[hash_for_segmentid({keyonly, {o, "Bucket1", "Key99"}})],
|
[hash_for_segmentid({keyonly,
|
||||||
|
{o, "Bucket1", "Key99", null}})],
|
||||||
true),
|
true),
|
||||||
?assertMatch(R1, not_present),
|
?assertMatch(R1, not_present),
|
||||||
R2 = check_for_segments(serialise_segment_filter(SegFilter),
|
R2 = check_for_segments(serialise_segment_filter(SegFilter),
|
||||||
[hash_for_segmentid({keyonly, {o, "BucketSeq", "Key00000040"}})],
|
[hash_for_segmentid({keyonly,
|
||||||
|
{o, "BucketSeq", "Key00000040", null}})],
|
||||||
true),
|
true),
|
||||||
?assertMatch(R2, {maybe_present, [1]}),
|
?assertMatch(R2, {maybe_present, [1]}),
|
||||||
R3 = check_for_segments(serialise_segment_filter(SegFilter),
|
R3 = check_for_segments(serialise_segment_filter(SegFilter),
|
||||||
[hash_for_segmentid({keyonly, {o, "BucketSeq", "Key00000004"}})],
|
[hash_for_segmentid({keyonly,
|
||||||
|
{o, "BucketSeq", "Key00000004", null}})],
|
||||||
true),
|
true),
|
||||||
?assertMatch(R3, {maybe_present, [0]}).
|
?assertMatch(R3, {maybe_present, [0]}).
|
||||||
|
|
||||||
|
@ -1640,11 +1638,11 @@ writekeys_stage1_test() ->
|
||||||
fun testwrite_function/2),
|
fun testwrite_function/2),
|
||||||
{Handle, {_, PointerIndex}, SNExtremes, KeyExtremes} = FunOut,
|
{Handle, {_, PointerIndex}, SNExtremes, KeyExtremes} = FunOut,
|
||||||
?assertMatch(SNExtremes, {1,3}),
|
?assertMatch(SNExtremes, {1,3}),
|
||||||
?assertMatch(KeyExtremes, {{o, "Bucket1", "Key1"},
|
?assertMatch(KeyExtremes, {{o, "Bucket1", "Key1", null},
|
||||||
{o, "Bucket4", "Key1"}}),
|
{o, "Bucket4", "Key1", null}}),
|
||||||
[TopIndex|[]] = PointerIndex,
|
[TopIndex|[]] = PointerIndex,
|
||||||
{TopKey, _SegFilter, {LengthList, _Total}} = TopIndex,
|
{TopKey, _SegFilter, {LengthList, _Total}} = TopIndex,
|
||||||
?assertMatch(TopKey, {o, "Bucket1", "Key1"}),
|
?assertMatch(TopKey, {o, "Bucket1", "Key1", null}),
|
||||||
TotalLength = lists:foldl(fun(X, Acc) -> Acc + X end,
|
TotalLength = lists:foldl(fun(X, Acc) -> Acc + X end,
|
||||||
0, LengthList),
|
0, LengthList),
|
||||||
ActualLength = lists:foldl(fun(X, Acc) -> Acc + byte_size(X) end,
|
ActualLength = lists:foldl(fun(X, Acc) -> Acc + byte_size(X) end,
|
||||||
|
@ -1660,11 +1658,11 @@ initial_create_file_test() ->
|
||||||
{KL1, KL2} = sample_keylist(),
|
{KL1, KL2} = sample_keylist(),
|
||||||
{Handle, FileMD} = create_file(Filename),
|
{Handle, FileMD} = create_file(Filename),
|
||||||
{UpdHandle, UpdFileMD, {[], []}} = complete_file(Handle, FileMD, KL1, KL2, 1),
|
{UpdHandle, UpdFileMD, {[], []}} = complete_file(Handle, FileMD, KL1, KL2, 1),
|
||||||
Result1 = fetch_keyvalue(UpdHandle, UpdFileMD, {o, "Bucket1", "Key8"}),
|
Result1 = fetch_keyvalue(UpdHandle, UpdFileMD, {o, "Bucket1", "Key8", null}),
|
||||||
io:format("Result is ~w~n", [Result1]),
|
io:format("Result is ~w~n", [Result1]),
|
||||||
?assertMatch(Result1, {{o, "Bucket1", "Key8"},
|
?assertMatch(Result1, {{o, "Bucket1", "Key8", null},
|
||||||
{1, {active, infinity}, null}}),
|
{1, {active, infinity}, null}}),
|
||||||
Result2 = fetch_keyvalue(UpdHandle, UpdFileMD, {o, "Bucket1", "Key88"}),
|
Result2 = fetch_keyvalue(UpdHandle, UpdFileMD, {o, "Bucket1", "Key88", null}),
|
||||||
io:format("Result is ~w~n", [Result2]),
|
io:format("Result is ~w~n", [Result2]),
|
||||||
?assertMatch(Result2, not_present),
|
?assertMatch(Result2, not_present),
|
||||||
ok = file:close(UpdHandle),
|
ok = file:close(UpdHandle),
|
||||||
|
@ -1699,7 +1697,9 @@ big_create_file_test() ->
|
||||||
SubList),
|
SubList),
|
||||||
io:format("FailedFinds of ~w~n", [FailedFinds]),
|
io:format("FailedFinds of ~w~n", [FailedFinds]),
|
||||||
?assertMatch(FailedFinds, 0),
|
?assertMatch(FailedFinds, 0),
|
||||||
Result3 = fetch_keyvalue(Handle, FileMD, {o, "Bucket1024", "Key1024Alt"}),
|
Result3 = fetch_keyvalue(Handle,
|
||||||
|
FileMD,
|
||||||
|
{o, "Bucket1024", "Key1024Alt", null}),
|
||||||
?assertMatch(Result3, not_present),
|
?assertMatch(Result3, not_present),
|
||||||
ok = file:close(Handle),
|
ok = file:close(Handle),
|
||||||
ok = file:delete(Filename).
|
ok = file:delete(Filename).
|
||||||
|
@ -1708,35 +1708,46 @@ initial_iterator_test() ->
|
||||||
Filename = "../test/test2.sft",
|
Filename = "../test/test2.sft",
|
||||||
{KL1, KL2} = sample_keylist(),
|
{KL1, KL2} = sample_keylist(),
|
||||||
{Handle, FileMD} = create_file(Filename),
|
{Handle, FileMD} = create_file(Filename),
|
||||||
{UpdHandle, UpdFileMD, {[], []}} = complete_file(Handle, FileMD, KL1, KL2, 1),
|
{UpdHandle, UpdFileMD, {[], []}} = complete_file(Handle,
|
||||||
|
FileMD,
|
||||||
|
KL1,
|
||||||
|
KL2,
|
||||||
|
1),
|
||||||
Result1 = fetch_range_keysonly(UpdHandle, UpdFileMD,
|
Result1 = fetch_range_keysonly(UpdHandle, UpdFileMD,
|
||||||
{o, "Bucket1", "Key8"},
|
{o, "Bucket1", "Key8", null},
|
||||||
{o, "Bucket1", "Key9d"}),
|
{o, "Bucket1", "Key9d", null}),
|
||||||
io:format("Result returned of ~w~n", [Result1]),
|
io:format("Result returned of ~w~n", [Result1]),
|
||||||
?assertMatch(Result1, {complete, [{{o, "Bucket1", "Key8"}, 1},
|
?assertMatch({complete,
|
||||||
{{o, "Bucket1", "Key9"}, 1},
|
[{{o, "Bucket1", "Key8", null}, 1, {active, infinity}},
|
||||||
{{o, "Bucket1", "Key9a"}, 1},
|
{{o, "Bucket1", "Key9", null}, 1, {active, infinity}},
|
||||||
{{o, "Bucket1", "Key9b"}, 1},
|
{{o, "Bucket1", "Key9a", null}, 1, {active, infinity}},
|
||||||
{{o, "Bucket1", "Key9c"}, 1},
|
{{o, "Bucket1", "Key9b", null}, 1, {active, infinity}},
|
||||||
{{o, "Bucket1", "Key9d"}, 1}]}),
|
{{o, "Bucket1", "Key9c", null}, 1, {active, infinity}},
|
||||||
|
{{o, "Bucket1", "Key9d", null}, 1, {active, infinity}}
|
||||||
|
]},
|
||||||
|
Result1),
|
||||||
Result2 = fetch_range_keysonly(UpdHandle, UpdFileMD,
|
Result2 = fetch_range_keysonly(UpdHandle, UpdFileMD,
|
||||||
{o, "Bucket1", "Key8"},
|
{o, "Bucket1", "Key8", null},
|
||||||
{o, "Bucket1", "Key9b"}),
|
{o, "Bucket1", "Key9b", null}),
|
||||||
?assertMatch(Result2, {complete, [{{o, "Bucket1", "Key8"}, 1},
|
?assertMatch({complete,
|
||||||
{{o, "Bucket1", "Key9"}, 1},
|
[{{o, "Bucket1", "Key8", null}, 1, {active, infinity}},
|
||||||
{{o, "Bucket1", "Key9a"}, 1},
|
{{o, "Bucket1", "Key9", null}, 1, {active, infinity}},
|
||||||
{{o, "Bucket1", "Key9b"}, 1}]}),
|
{{o, "Bucket1", "Key9a", null}, 1, {active, infinity}},
|
||||||
|
{{o, "Bucket1", "Key9b", null}, 1, {active, infinity}}
|
||||||
|
]},
|
||||||
|
Result2),
|
||||||
Result3 = fetch_range_keysonly(UpdHandle, UpdFileMD,
|
Result3 = fetch_range_keysonly(UpdHandle, UpdFileMD,
|
||||||
{o, "Bucket3", "Key4"},
|
{o, "Bucket3", "Key4", null},
|
||||||
all),
|
all),
|
||||||
{partial, RL3, _} = Result3,
|
{partial, RL3, _} = Result3,
|
||||||
?assertMatch(RL3, [{{o, "Bucket3", "Key4"}, 3},
|
?assertMatch([{{o, "Bucket3", "Key4", null}, 3, {active, infinity}},
|
||||||
{{o, "Bucket3", "Key5"}, 1},
|
{{o, "Bucket3", "Key5", null}, 1, {active, infinity}},
|
||||||
{{o, "Bucket3", "Key6"}, 2},
|
{{o, "Bucket3", "Key6", null}, 2, {active, infinity}},
|
||||||
{{o, "Bucket3", "Key7"}, 1},
|
{{o, "Bucket3", "Key7", null}, 1, {active, infinity}},
|
||||||
{{o, "Bucket3", "Key8"}, 1},
|
{{o, "Bucket3", "Key8", null}, 1, {active, infinity}},
|
||||||
{{o, "Bucket3", "Key9"}, 1},
|
{{o, "Bucket3", "Key9", null}, 1, {active, infinity}},
|
||||||
{{o, "Bucket4", "Key1"}, 1}]),
|
{{o, "Bucket4", "Key1", null}, 1, {active, infinity}}],
|
||||||
|
RL3),
|
||||||
ok = file:close(UpdHandle),
|
ok = file:close(UpdHandle),
|
||||||
ok = file:delete(Filename).
|
ok = file:delete(Filename).
|
||||||
|
|
||||||
|
@ -1748,22 +1759,26 @@ big_iterator_test() ->
|
||||||
InitFileMD,
|
InitFileMD,
|
||||||
KL1, KL2, 1),
|
KL1, KL2, 1),
|
||||||
io:format("Remainder lengths are ~w and ~w ~n", [length(KL1Rem), length(KL2Rem)]),
|
io:format("Remainder lengths are ~w and ~w ~n", [length(KL1Rem), length(KL2Rem)]),
|
||||||
{complete, Result1} = fetch_range_keysonly(Handle, FileMD, {o, "Bucket0000", "Key0000"},
|
{complete, Result1} = fetch_range_keysonly(Handle,
|
||||||
{o, "Bucket9999", "Key9999"},
|
FileMD,
|
||||||
|
{o, "Bucket0000", "Key0000", null},
|
||||||
|
{o, "Bucket9999", "Key9999", null},
|
||||||
256),
|
256),
|
||||||
NumFoundKeys1 = length(Result1),
|
NumFoundKeys1 = length(Result1),
|
||||||
NumAddedKeys = 10000 - length(KL1Rem),
|
NumAddedKeys = 10000 - length(KL1Rem),
|
||||||
?assertMatch(NumFoundKeys1, NumAddedKeys),
|
?assertMatch(NumFoundKeys1, NumAddedKeys),
|
||||||
{partial, Result2, _} = fetch_range_keysonly(Handle, FileMD, {o, "Bucket0000", "Key0000"},
|
{partial, Result2, _} = fetch_range_keysonly(Handle,
|
||||||
{o, "Bucket9999", "Key9999"},
|
FileMD,
|
||||||
|
{o, "Bucket0000", "Key0000", null},
|
||||||
|
{o, "Bucket9999", "Key9999", null},
|
||||||
32),
|
32),
|
||||||
NumFoundKeys2 = length(Result2),
|
?assertMatch(32 * 128, length(Result2)),
|
||||||
?assertMatch(NumFoundKeys2, 32 * 128),
|
{partial, Result3, _} = fetch_range_keysonly(Handle,
|
||||||
{partial, Result3, _} = fetch_range_keysonly(Handle, FileMD, {o, "Bucket0000", "Key0000"},
|
FileMD,
|
||||||
{o, "Bucket9999", "Key9999"},
|
{o, "Bucket0000", "Key0000", null},
|
||||||
|
{o, "Bucket9999", "Key9999", null},
|
||||||
4),
|
4),
|
||||||
NumFoundKeys3 = length(Result3),
|
?assertMatch(4 * 128, length(Result3)),
|
||||||
?assertMatch(NumFoundKeys3, 4 * 128),
|
|
||||||
ok = file:close(Handle),
|
ok = file:close(Handle),
|
||||||
ok = file:delete(Filename).
|
ok = file:delete(Filename).
|
||||||
|
|
||||||
|
|
|
@ -7,9 +7,9 @@
|
||||||
journal_compaction/1,
|
journal_compaction/1,
|
||||||
fetchput_snapshot/1]).
|
fetchput_snapshot/1]).
|
||||||
|
|
||||||
all() -> [simple_put_fetch_head,
|
all() -> [% simple_put_fetch_head,
|
||||||
many_put_fetch_head,
|
% many_put_fetch_head,
|
||||||
journal_compaction,
|
% journal_compaction,
|
||||||
fetchput_snapshot].
|
fetchput_snapshot].
|
||||||
|
|
||||||
|
|
||||||
|
@ -203,6 +203,15 @@ fetchput_snapshot(_Config) ->
|
||||||
{ok, FNsC} = file:list_dir(RootPath ++ "/ledger/ledger_files"),
|
{ok, FNsC} = file:list_dir(RootPath ++ "/ledger/ledger_files"),
|
||||||
true = length(FNsB) > length(FNsA),
|
true = length(FNsB) > length(FNsA),
|
||||||
true = length(FNsB) > length(FNsC),
|
true = length(FNsB) > length(FNsC),
|
||||||
|
|
||||||
|
{B1Size, B1Count} = check_bucket_stats(Bookie2, "Bucket1"),
|
||||||
|
true = B1Size > 0,
|
||||||
|
true = B1Count == 1,
|
||||||
|
{B1Size, B1Count} = check_bucket_stats(Bookie2, "Bucket1"),
|
||||||
|
{BSize, BCount} = check_bucket_stats(Bookie2, "Bucket"),
|
||||||
|
true = BSize > 0,
|
||||||
|
true = BCount == 140000,
|
||||||
|
|
||||||
ok = leveled_bookie:book_close(Bookie2),
|
ok = leveled_bookie:book_close(Bookie2),
|
||||||
reset_filestructure().
|
reset_filestructure().
|
||||||
|
|
||||||
|
@ -217,6 +226,21 @@ reset_filestructure() ->
|
||||||
RootPath.
|
RootPath.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
check_bucket_stats(Bookie, Bucket) ->
|
||||||
|
FoldSW1 = os:timestamp(),
|
||||||
|
io:format("Checking bucket size~n"),
|
||||||
|
{async, Folder1} = leveled_bookie:book_returnfolder(Bookie,
|
||||||
|
{bucket_stats,
|
||||||
|
Bucket}),
|
||||||
|
{B1Size, B1Count} = Folder1(),
|
||||||
|
io:format("Bucket fold completed in ~w microseconds~n",
|
||||||
|
[timer:now_diff(os:timestamp(), FoldSW1)]),
|
||||||
|
io:format("Bucket ~w has size ~w and count ~w~n",
|
||||||
|
[Bucket, B1Size, B1Count]),
|
||||||
|
{B1Size, B1Count}.
|
||||||
|
|
||||||
|
|
||||||
check_bookie_forlist(Bookie, ChkList) ->
|
check_bookie_forlist(Bookie, ChkList) ->
|
||||||
check_bookie_forlist(Bookie, ChkList, false).
|
check_bookie_forlist(Bookie, ChkList, false).
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue