feat(queue): replace invalid rewing algorithm with skipping iterator
This commit is contained in:
parent
c569625dd1
commit
143086b0ef
|
@ -341,7 +341,11 @@ accept_stream(#{topic_filter := TopicFilter} = Event, S, ScheduledActions) ->
|
||||||
end.
|
end.
|
||||||
|
|
||||||
accept_stream(
|
accept_stream(
|
||||||
#{topic_filter := TopicFilter, stream := Stream, progress := Progress} = _Event,
|
#{
|
||||||
|
topic_filter := TopicFilter,
|
||||||
|
stream := Stream,
|
||||||
|
progress := #{iterator := Iterator} = _Progress
|
||||||
|
} = _Event,
|
||||||
S0
|
S0
|
||||||
) ->
|
) ->
|
||||||
case emqx_persistent_session_ds_state:get_subscription(TopicFilter, S0) of
|
case emqx_persistent_session_ds_state:get_subscription(TopicFilter, S0) of
|
||||||
|
@ -361,7 +365,6 @@ accept_stream(
|
||||||
end,
|
end,
|
||||||
case NeedCreateStream of
|
case NeedCreateStream of
|
||||||
true ->
|
true ->
|
||||||
Iterator = rewind_iterator(Progress),
|
|
||||||
NewSRS =
|
NewSRS =
|
||||||
#srs{
|
#srs{
|
||||||
rank_x = ?rank_x,
|
rank_x = ?rank_x,
|
||||||
|
@ -377,52 +380,6 @@ accept_stream(
|
||||||
end
|
end
|
||||||
end.
|
end.
|
||||||
|
|
||||||
%% Skip acked messages.
|
|
||||||
%% This may be a bit inefficient, and it is unclear how to handle errors.
|
|
||||||
%%
|
|
||||||
%% A better variant would be to wrap the iterator on `emqx_ds` level in a new one,
|
|
||||||
%% that will skip acked messages internally in `emqx_ds:next` function.
|
|
||||||
%% Unluckily, emqx_ds does not have a wrapping structure around iterators of
|
|
||||||
%% the underlying levels, so we cannot wrap it without a risk of confusion.
|
|
||||||
|
|
||||||
rewind_iterator(#{iterator := Iterator, acked := true}) ->
|
|
||||||
Iterator;
|
|
||||||
rewind_iterator(#{iterator := Iterator0, acked := false, qos1_acked := 0, qos2_acked := 0}) ->
|
|
||||||
Iterator0;
|
|
||||||
%% This should not happen, means the DS is consistent
|
|
||||||
rewind_iterator(#{iterator := Iterator0, acked := false, qos1_acked := Q1, qos2_acked := Q2}) when
|
|
||||||
Q1 < 0 orelse Q2 < 0
|
|
||||||
->
|
|
||||||
Iterator0;
|
|
||||||
rewind_iterator(
|
|
||||||
#{iterator := Iterator0, acked := false, qos1_acked := Q1Old, qos2_acked := Q2Old} = Progress
|
|
||||||
) ->
|
|
||||||
case emqx_ds:next(?PERSISTENT_MESSAGE_DB, Iterator0, Q1Old + Q2Old) of
|
|
||||||
{ok, Iterator1, Messages} ->
|
|
||||||
{Q1New, Q2New} = update_qos_acked(Q1Old, Q2Old, Messages),
|
|
||||||
rewind_iterator(Progress#{
|
|
||||||
iterator => Iterator1, qos1_acked => Q1New, qos2_acked => Q2New
|
|
||||||
});
|
|
||||||
{ok, end_of_stream} ->
|
|
||||||
end_of_stream;
|
|
||||||
{error, _, _} ->
|
|
||||||
%% What to do here?
|
|
||||||
%% In the wrapping variant we do not have this problem.
|
|
||||||
Iterator0
|
|
||||||
end.
|
|
||||||
|
|
||||||
update_qos_acked(Q1, Q2, []) ->
|
|
||||||
{Q1, Q2};
|
|
||||||
update_qos_acked(Q1, Q2, [{_Key, Message} | Messages]) ->
|
|
||||||
case emqx_message:qos(Message) of
|
|
||||||
?QOS_1 ->
|
|
||||||
update_qos_acked(Q1 - 1, Q2, Messages);
|
|
||||||
?QOS_2 ->
|
|
||||||
update_qos_acked(Q1, Q2 - 1, Messages);
|
|
||||||
_ ->
|
|
||||||
update_qos_acked(Q1, Q2, Messages)
|
|
||||||
end.
|
|
||||||
|
|
||||||
revoke_stream(
|
revoke_stream(
|
||||||
#{topic_filter := TopicFilter, stream := Stream}, S0
|
#{topic_filter := TopicFilter, stream := Stream}, S0
|
||||||
) ->
|
) ->
|
||||||
|
@ -667,8 +624,8 @@ stream_progress(
|
||||||
first_seqno_qos2 = StartQos2
|
first_seqno_qos2 = StartQos2
|
||||||
} = SRS
|
} = SRS
|
||||||
) ->
|
) ->
|
||||||
Qos1Acked = seqno_diff(?QOS_1, CommQos1, StartQos1),
|
Qos1Acked = n_acked(?QOS_1, CommQos1, StartQos1),
|
||||||
Qos2Acked = seqno_diff(?QOS_2, CommQos2, StartQos2),
|
Qos2Acked = n_acked(?QOS_2, CommQos2, StartQos2),
|
||||||
case is_stream_fully_acked(CommQos1, CommQos2, SRS) of
|
case is_stream_fully_acked(CommQos1, CommQos2, SRS) of
|
||||||
true ->
|
true ->
|
||||||
#{
|
#{
|
||||||
|
@ -683,10 +640,10 @@ stream_progress(
|
||||||
#{
|
#{
|
||||||
stream => Stream,
|
stream => Stream,
|
||||||
progress => #{
|
progress => #{
|
||||||
acked => false,
|
acked => true,
|
||||||
iterator => BeginIt,
|
iterator => emqx_ds_skipping_iterator:update_or_new(
|
||||||
qos1_acked => Qos1Acked,
|
BeginIt, Qos1Acked, Qos2Acked
|
||||||
qos2_acked => Qos2Acked
|
)
|
||||||
},
|
},
|
||||||
use_finished => is_use_finished(SRS)
|
use_finished => is_use_finished(SRS)
|
||||||
}
|
}
|
||||||
|
@ -753,6 +710,9 @@ is_stream_fully_acked(_, _, #srs{
|
||||||
is_stream_fully_acked(Comm1, Comm2, #srs{last_seqno_qos1 = S1, last_seqno_qos2 = S2}) ->
|
is_stream_fully_acked(Comm1, Comm2, #srs{last_seqno_qos1 = S1, last_seqno_qos2 = S2}) ->
|
||||||
(Comm1 >= S1) andalso (Comm2 >= S2).
|
(Comm1 >= S1) andalso (Comm2 >= S2).
|
||||||
|
|
||||||
|
n_acked(Qos, A, B) ->
|
||||||
|
max(seqno_diff(Qos, A, B), 0).
|
||||||
|
|
||||||
-dialyzer({nowarn_function, seqno_diff/3}).
|
-dialyzer({nowarn_function, seqno_diff/3}).
|
||||||
seqno_diff(?QOS_1, A, B) ->
|
seqno_diff(?QOS_1, A, B) ->
|
||||||
%% For QoS1 messages we skip a seqno every time the epoch changes,
|
%% For QoS1 messages we skip a seqno every time the epoch changes,
|
||||||
|
|
|
@ -401,10 +401,14 @@ make_iterator(DB, Stream, TopicFilter, StartTime) ->
|
||||||
|
|
||||||
-spec update_iterator(db(), iterator(), message_key()) ->
|
-spec update_iterator(db(), iterator(), message_key()) ->
|
||||||
make_iterator_result().
|
make_iterator_result().
|
||||||
|
update_iterator(DB, ?skipping_iterator_match = OldIter, DSKey) ->
|
||||||
|
emqx_ds_skipping_iterator:update_iterator(DB, OldIter, DSKey);
|
||||||
update_iterator(DB, OldIter, DSKey) ->
|
update_iterator(DB, OldIter, DSKey) ->
|
||||||
?module(DB):update_iterator(DB, OldIter, DSKey).
|
?module(DB):update_iterator(DB, OldIter, DSKey).
|
||||||
|
|
||||||
-spec next(db(), iterator(), pos_integer()) -> next_result().
|
-spec next(db(), iterator(), pos_integer()) -> next_result().
|
||||||
|
next(DB, ?skipping_iterator_match = Iter, BatchSize) ->
|
||||||
|
emqx_ds_skipping_iterator:next(DB, Iter, BatchSize);
|
||||||
next(DB, Iter, BatchSize) ->
|
next(DB, Iter, BatchSize) ->
|
||||||
?module(DB):next(DB, Iter, BatchSize).
|
?module(DB):next(DB, Iter, BatchSize).
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,87 @@
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% Copyright (c) 2023-2024 EMQ Technologies Co., Ltd. All Rights Reserved.
|
||||||
|
%%
|
||||||
|
%% Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
%% you may not use this file except in compliance with the License.
|
||||||
|
%% You may obtain a copy of the License at
|
||||||
|
%%
|
||||||
|
%% http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
%%
|
||||||
|
%% Unless required by applicable law or agreed to in writing, software
|
||||||
|
%% distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
%% See the License for the specific language governing permissions and
|
||||||
|
%% limitations under the License.
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
-module(emqx_ds_skipping_iterator).
|
||||||
|
|
||||||
|
-include("emqx_ds_skipping_iterator.hrl").
|
||||||
|
-include("emqx/include/emqx_mqtt.hrl").
|
||||||
|
|
||||||
|
-type t() :: ?skipping_iterator(emqx_ds:iterator(), non_neg_integer(), non_neg_integer()).
|
||||||
|
|
||||||
|
-export([
|
||||||
|
update_or_new/3,
|
||||||
|
update_iterator/3,
|
||||||
|
next/3
|
||||||
|
]).
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% API
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
-spec update_or_new(t() | emqx_ds:iterator(), non_neg_integer(), non_neg_integer()) -> t().
|
||||||
|
update_or_new(?skipping_iterator_match(Iterator, Q1Skip0, Q2Skip0), Q1Skip, Q2Skip) when
|
||||||
|
Q1Skip >= 0 andalso Q2Skip >= 0
|
||||||
|
->
|
||||||
|
?skipping_iterator(Iterator, Q1Skip0 + Q1Skip, Q2Skip0 + Q2Skip);
|
||||||
|
update_or_new(Iterator, Q1Skip, Q2Skip) when Q1Skip >= 0 andalso Q2Skip >= 0 ->
|
||||||
|
?skipping_iterator(Iterator, Q1Skip, Q2Skip).
|
||||||
|
|
||||||
|
-spec next(emqx_ds:db(), t(), pos_integer()) -> emqx_ds:next_result(t()).
|
||||||
|
next(DB, ?skipping_iterator_match(Iterator0, Q1Skip0, Q2Skip0), Count) ->
|
||||||
|
case emqx_ds:next(DB, Iterator0, Count) of
|
||||||
|
{error, _, _} = Error ->
|
||||||
|
Error;
|
||||||
|
{ok, end_of_stream} ->
|
||||||
|
{ok, end_of_stream};
|
||||||
|
{ok, Iterator1, Messages0} ->
|
||||||
|
{Messages1, Q1Skip1, Q2Skip1} = skip(Messages0, Q1Skip0, Q2Skip0),
|
||||||
|
case {Q1Skip1, Q2Skip1} of
|
||||||
|
{0, 0} -> {ok, Iterator1, Messages1};
|
||||||
|
_ -> {ok, ?skipping_iterator(Iterator1, Q1Skip1, Q2Skip1)}
|
||||||
|
end
|
||||||
|
end.
|
||||||
|
|
||||||
|
-spec update_iterator(emqx_ds:db(), emqx_ds:iterator(), emqx_ds:message_key()) ->
|
||||||
|
emqx_ds:make_iterator_result().
|
||||||
|
update_iterator(DB, ?skipping_iterator_match(Iterator0, Q1Skip0, Q2Skip0), Key) ->
|
||||||
|
case emqx_ds:update_iterator(DB, Iterator0, Key) of
|
||||||
|
{error, _, _} = Error -> Error;
|
||||||
|
{ok, Iterator1} -> {ok, ?skipping_iterator(Iterator1, Q1Skip0, Q2Skip0)}
|
||||||
|
end.
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% Internal functions
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
skip(Messages, Q1Skip, Q2Skip) ->
|
||||||
|
skip(Messages, Q1Skip, Q2Skip, []).
|
||||||
|
|
||||||
|
skip([], Q1Skip, Q2Skip, Agg) ->
|
||||||
|
{lists:reverse(Agg), Q1Skip, Q2Skip};
|
||||||
|
skip([{Key, Message} | Messages], Q1Skip, Q2Skip, Agg) ->
|
||||||
|
Qos = emqx_message:qos(Message),
|
||||||
|
skip({Key, Message}, Qos, Messages, Q1Skip, Q2Skip, Agg).
|
||||||
|
|
||||||
|
skip(_KeyMessage, ?QOS_0, Messages, Q1Skip, Q2Skip, Agg) ->
|
||||||
|
skip(Messages, Q1Skip, Q2Skip, Agg);
|
||||||
|
skip(_KeyMessage, ?QOS_1, Messages, Q1Skip, Q2Skip, Agg) when Q1Skip > 0 ->
|
||||||
|
skip(Messages, Q1Skip - 1, Q2Skip, Agg);
|
||||||
|
skip(KeyMessage, ?QOS_1, Messages, 0, Q2Skip, Agg) ->
|
||||||
|
skip(Messages, 0, Q2Skip, [KeyMessage | Agg]);
|
||||||
|
skip(_KeyMessage, ?QOS_2, Messages, Q1Skip, Q2Skip, Agg) when Q2Skip > 0 ->
|
||||||
|
skip(Messages, Q1Skip, Q2Skip - 1, Agg);
|
||||||
|
skip(KeyMessage, ?QOS_2, Messages, Q1Skip, 0, Agg) ->
|
||||||
|
skip(Messages, Q1Skip, 0, [KeyMessage | Agg]).
|
|
@ -0,0 +1,32 @@
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% Copyright (c) 2023-2024 EMQ Technologies Co., Ltd. All Rights Reserved.
|
||||||
|
%%
|
||||||
|
%% Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
%% you may not use this file except in compliance with the License.
|
||||||
|
%% You may obtain a copy of the License at
|
||||||
|
%%
|
||||||
|
%% http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
%%
|
||||||
|
%% Unless required by applicable law or agreed to in writing, software
|
||||||
|
%% distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
%% See the License for the specific language governing permissions and
|
||||||
|
%% limitations under the License.
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
-define(tag, 1).
|
||||||
|
-define(it, 2).
|
||||||
|
-define(qos1_skip, 3).
|
||||||
|
-define(qos2_skip, 4).
|
||||||
|
|
||||||
|
-define(IT, -1000).
|
||||||
|
|
||||||
|
-define(skipping_iterator_match, #{?tag := ?IT}).
|
||||||
|
|
||||||
|
-define(skipping_iterator_match(Iterator, Q1Skip, Q2Skip), #{
|
||||||
|
?tag := ?IT, ?it := Iterator, ?qos1_skip := Q1Skip, ?qos2_skip := Q2Skip
|
||||||
|
}).
|
||||||
|
|
||||||
|
-define(skipping_iterator(Iterator, Q1Skip, Q2Skip), #{
|
||||||
|
?tag => ?IT, ?it => Iterator, ?qos1_skip => Q1Skip, ?qos2_skip => Q2Skip
|
||||||
|
}).
|
Loading…
Reference in New Issue