chore(ds): change return type of `storage_layer:next/{1,2}`

Part of https://emqx.atlassian.net/browse/EMQX-10942

The goal is to help make it clear to the caller of `next` what to do next: if the iterator
should still be used or if no new messages will ever come out of it.

From:

```erlang
-spec next(iterator()) -> {value, binary(), iterator()} | none | {error, closed}.
```

To:

```erlang
-spec next(iterator()) -> {ok, iterator(), [binary()]} | end_of_stream.

-spec next(iterator(), pos_integer()) -> {ok, iterator(), [binary()]} | end_of_stream.
```
This commit is contained in:
Thales Macedo Garitezi 2023-10-02 16:20:16 -03:00 committed by ieQu1
parent 03358b77aa
commit 7ab57824dc
5 changed files with 76 additions and 40 deletions

View File

@ -272,9 +272,9 @@ consume(Shard, IteratorId) when is_binary(IteratorId) ->
consume(It) -> consume(It) ->
case emqx_ds_storage_layer:next(It) of case emqx_ds_storage_layer:next(It) of
{value, Msg, NIt} -> {ok, NIt, [Msg]} ->
[emqx_persistent_message:deserialize(Msg) | consume(NIt)]; [emqx_persistent_message:deserialize(Msg) | consume(NIt)];
none -> end_of_stream ->
[] []
end. end.

View File

@ -90,7 +90,7 @@
-export([next/1]). -export([next/1]).
-export([preserve_iterator/1]). -export([preserve_iterator/1]).
-export([restore_iterator/3]). -export([restore_iterator/2]).
-export([refresh_iterator/1]). -export([refresh_iterator/1]).
%% Debug/troubleshooting: %% Debug/troubleshooting:
@ -217,6 +217,7 @@
-opaque db() :: #db{}. -opaque db() :: #db{}.
-opaque iterator() :: #it{}. -opaque iterator() :: #it{}.
-type serialized_iterator() :: binary().
-type keymapper() :: #keymapper{}. -type keymapper() :: #keymapper{}.
-type keyspace_filter() :: #filter{}. -type keyspace_filter() :: #filter{}.
@ -340,22 +341,30 @@ next(It0 = #it{filter = #filter{keymapper = Keymapper}}) ->
{error, closed} {error, closed}
end. end.
-spec preserve_iterator(iterator()) -> binary(). -spec preserve_iterator(iterator()) -> serialized_iterator().
preserve_iterator(#it{cursor = Cursor}) -> preserve_iterator(#it{
cursor = Cursor,
filter = #filter{
topic_filter = TopicFilter,
start_time = StartTime
}
}) ->
State = #{ State = #{
v => 1, v => 1,
cursor => Cursor cursor => Cursor,
replay => {TopicFilter, StartTime}
}, },
term_to_binary(State). term_to_binary(State).
-spec restore_iterator(db(), emqx_ds:replay(), binary()) -> -spec restore_iterator(db(), serialized_iterator()) ->
{ok, iterator()} | {error, _TODO}. {ok, iterator()} | {error, _TODO}.
restore_iterator(DB, Replay, Serial) when is_binary(Serial) -> restore_iterator(DB, Serial) when is_binary(Serial) ->
State = binary_to_term(Serial), State = binary_to_term(Serial),
restore_iterator(DB, Replay, State); restore_iterator(DB, State);
restore_iterator(DB, Replay, #{ restore_iterator(DB, #{
v := 1, v := 1,
cursor := Cursor cursor := Cursor,
replay := Replay = {_TopicFilter, _StartTime}
}) -> }) ->
case make_iterator(DB, Replay) of case make_iterator(DB, Replay) of
{ok, It} when Cursor == undefined -> {ok, It} when Cursor == undefined ->

View File

@ -12,7 +12,7 @@
-export([store/5]). -export([store/5]).
-export([delete/4]). -export([delete/4]).
-export([make_iterator/2, next/1]). -export([make_iterator/2, next/1, next/2]).
-export([ -export([
preserve_iterator/2, preserve_iterator/2,
@ -131,7 +131,7 @@
-callback make_iterator(_Schema, emqx_ds:replay()) -> -callback make_iterator(_Schema, emqx_ds:replay()) ->
{ok, _It} | {error, _}. {ok, _It} | {error, _}.
-callback restore_iterator(_Schema, emqx_ds:replay(), binary()) -> {ok, _It} | {error, _}. -callback restore_iterator(_Schema, _Serialized :: binary()) -> {ok, _It} | {error, _}.
-callback preserve_iterator(_It) -> term(). -callback preserve_iterator(_It) -> term().
@ -175,21 +175,52 @@ make_iterator(Shard, Replay = {_, StartTime}) ->
replay = Replay replay = Replay
}). }).
-spec next(iterator()) -> {value, binary(), iterator()} | none | {error, closed}. -spec next(iterator()) -> {ok, iterator(), [binary()]} | end_of_stream.
next(It = #it{module = Mod, data = ItData}) -> next(It = #it{}) ->
next(It, _BatchSize = 1).
-spec next(iterator(), pos_integer()) -> {ok, iterator(), [binary()]} | end_of_stream.
next(#it{data = {?MODULE, end_of_stream}}, _BatchSize) ->
end_of_stream;
next(
It = #it{shard = Shard, module = Mod, gen = Gen, data = {?MODULE, retry, Serialized}}, BatchSize
) ->
#{data := DBData} = meta_get_gen(Shard, Gen),
{ok, ItData} = Mod:restore_iterator(DBData, Serialized),
next(It#it{data = ItData}, BatchSize);
next(It = #it{}, BatchSize) ->
do_next(It, BatchSize, _Acc = []).
-spec do_next(iterator(), non_neg_integer(), [binary()]) ->
{ok, iterator(), [binary()]} | end_of_stream.
do_next(It, N, Acc) when N =< 0 ->
{ok, It, lists:reverse(Acc)};
do_next(It = #it{module = Mod, data = ItData}, N, Acc) ->
case Mod:next(ItData) of case Mod:next(ItData) of
{value, Val, ItDataNext} -> {value, Val, ItDataNext} ->
{value, Val, It#it{data = ItDataNext}}; do_next(It#it{data = ItDataNext}, N - 1, [Val | Acc]);
{error, _} = Error -> {error, _} = _Error ->
Error; %% todo: log?
%% iterator might be invalid now; will need to re-open it.
Serialized = Mod:preserve_iterator(ItData),
{ok, It#it{data = {?MODULE, retry, Serialized}}, lists:reverse(Acc)};
none -> none ->
case open_next_iterator(It) of case open_next_iterator(It) of
{ok, ItNext} -> {ok, ItNext} ->
next(ItNext); do_next(ItNext, N, Acc);
{error, _} = Error -> {error, _} = _Error ->
Error; %% todo: log?
%% fixme: only bad options may lead to this?
%% return an "empty" iterator to be re-opened when retrying?
Serialized = Mod:preserve_iterator(ItData),
{ok, It#it{data = {?MODULE, retry, Serialized}}, lists:reverse(Acc)};
none -> none ->
none case Acc of
[] ->
end_of_stream;
_ ->
{ok, It#it{data = {?MODULE, end_of_stream}}, lists:reverse(Acc)}
end
end end
end. end.
@ -407,8 +438,8 @@ open_iterator(#{module := Mod, data := Data}, It = #it{}) ->
-spec open_restore_iterator(generation(), iterator(), binary()) -> -spec open_restore_iterator(generation(), iterator(), binary()) ->
{ok, iterator()} | {error, _Reason}. {ok, iterator()} | {error, _Reason}.
open_restore_iterator(#{module := Mod, data := Data}, It = #it{replay = Replay}, Serial) -> open_restore_iterator(#{module := Mod, data := Data}, It = #it{}, Serial) ->
case Mod:restore_iterator(Data, Replay, Serial) of case Mod:restore_iterator(Data, Serial) of
{ok, ItData} -> {ok, ItData} ->
{ok, It#it{module = Mod, data = ItData}}; {ok, It#it{module = Mod, data = ItData}};
Err -> Err ->

View File

@ -201,7 +201,7 @@ t_iterate_multigen_preserve_restore(_Config) ->
ok = emqx_ds_storage_layer:preserve_iterator(It3, ReplayID), ok = emqx_ds_storage_layer:preserve_iterator(It3, ReplayID),
{ok, It4} = emqx_ds_storage_layer:restore_iterator(?SHARD, ReplayID), {ok, It4} = emqx_ds_storage_layer:restore_iterator(?SHARD, ReplayID),
{It5, Res200} = iterate(It4, 1000), {It5, Res200} = iterate(It4, 1000),
?assertEqual(none, It5), ?assertEqual({end_of_stream, []}, iterate(It5, 1)),
?assertEqual( ?assertEqual(
lists:sort([{Topic, TS} || Topic <- TopicsMatching, TS <- Timestamps]), lists:sort([{Topic, TS} || Topic <- TopicsMatching, TS <- Timestamps]),
lists:sort([binary_to_term(Payload) || Payload <- Res10 ++ Res100 ++ Res200]) lists:sort([binary_to_term(Payload) || Payload <- Res10 ++ Res100 ++ Res200])
@ -224,21 +224,20 @@ iterate(DB, TopicFilter, StartTime) ->
iterate(It) -> iterate(It) ->
case emqx_ds_storage_layer:next(It) of case emqx_ds_storage_layer:next(It) of
{value, Payload, ItNext} -> {ok, ItNext, [Payload]} ->
[Payload | iterate(ItNext)]; [Payload | iterate(ItNext)];
none -> end_of_stream ->
[] []
end. end.
iterate(It, 0) -> iterate(end_of_stream, _N) ->
{It, []}; {end_of_stream, []};
iterate(It, N) -> iterate(It, N) ->
case emqx_ds_storage_layer:next(It) of case emqx_ds_storage_layer:next(It, N) of
{value, Payload, ItNext} -> {ok, ItFinal, Payloads} ->
{ItFinal, Ps} = iterate(ItNext, N - 1), {ItFinal, Payloads};
{ItFinal, [Payload | Ps]}; end_of_stream ->
none -> {end_of_stream, []}
{none, []}
end. end.
iterator(DB, TopicFilter, StartTime) -> iterator(DB, TopicFilter, StartTime) ->

View File

@ -225,12 +225,9 @@ run_iterator_commands([iterate | Rest], It, Ctx) ->
[] []
end; end;
run_iterator_commands([{preserve, restore} | Rest], It, Ctx) -> run_iterator_commands([{preserve, restore} | Rest], It, Ctx) ->
#{ #{db := DB} = Ctx,
db := DB,
replay := Replay
} = Ctx,
Serial = emqx_ds_message_storage_bitmask:preserve_iterator(It), Serial = emqx_ds_message_storage_bitmask:preserve_iterator(It),
{ok, ItNext} = emqx_ds_message_storage_bitmask:restore_iterator(DB, Replay, Serial), {ok, ItNext} = emqx_ds_message_storage_bitmask:restore_iterator(DB, Serial),
run_iterator_commands(Rest, ItNext, Ctx); run_iterator_commands(Rest, ItNext, Ctx);
run_iterator_commands([], It, _Ctx) -> run_iterator_commands([], It, _Ctx) ->
iterate_db(It). iterate_db(It).