|
|
@@ -36,27 +36,36 @@
|
|
|
update_iterator/3,
|
|
|
next/3,
|
|
|
delete_next/4,
|
|
|
- node_of_shard/2,
|
|
|
- shard_of_message/3,
|
|
|
- maybe_set_myself_as_leader/2
|
|
|
+ shard_of_message/3
|
|
|
]).
|
|
|
|
|
|
%% internal exports:
|
|
|
-export([
|
|
|
+ %% RPC Targets:
|
|
|
do_drop_db_v1/1,
|
|
|
do_store_batch_v1/4,
|
|
|
do_get_streams_v1/4,
|
|
|
do_get_streams_v2/4,
|
|
|
- do_make_iterator_v1/5,
|
|
|
do_make_iterator_v2/5,
|
|
|
do_update_iterator_v2/4,
|
|
|
do_next_v1/4,
|
|
|
- do_add_generation_v2/1,
|
|
|
do_list_generations_with_lifetimes_v3/2,
|
|
|
- do_drop_generation_v3/3,
|
|
|
do_get_delete_streams_v4/4,
|
|
|
do_make_delete_iterator_v4/5,
|
|
|
- do_delete_next_v4/5
|
|
|
+ do_delete_next_v4/5,
|
|
|
+ %% Unused:
|
|
|
+ do_drop_generation_v3/3,
|
|
|
+ %% Obsolete:
|
|
|
+ do_make_iterator_v1/5,
|
|
|
+ do_add_generation_v2/1,
|
|
|
+
|
|
|
+ %% Egress API:
|
|
|
+ ra_store_batch/3
|
|
|
+]).
|
|
|
+
|
|
|
+-export([
|
|
|
+ init/1,
|
|
|
+ apply/3
|
|
|
]).
|
|
|
|
|
|
-export_type([
|
|
|
@@ -85,7 +94,9 @@
|
|
|
backend := builtin,
|
|
|
storage := emqx_ds_storage_layer:prototype(),
|
|
|
n_shards => pos_integer(),
|
|
|
- replication_factor => pos_integer()
|
|
|
+ n_sites => pos_integer(),
|
|
|
+ replication_factor => pos_integer(),
|
|
|
+ replication_options => _TODO :: #{}
|
|
|
}.
|
|
|
|
|
|
%% This enapsulates the stream entity from the replication level.
|
|
|
@@ -150,13 +161,19 @@ open_db(DB, CreateOpts) ->
|
|
|
|
|
|
-spec add_generation(emqx_ds:db()) -> ok | {error, _}.
|
|
|
add_generation(DB) ->
|
|
|
- Nodes = emqx_ds_replication_layer_meta:leader_nodes(DB),
|
|
|
- _ = emqx_ds_proto_v4:add_generation(Nodes, DB),
|
|
|
- ok.
|
|
|
+ foreach_shard(
|
|
|
+ DB,
|
|
|
+ fun(Shard) -> ok = ra_add_generation(DB, Shard) end
|
|
|
+ ).
|
|
|
|
|
|
-spec update_db_config(emqx_ds:db(), builtin_db_opts()) -> ok | {error, _}.
|
|
|
update_db_config(DB, CreateOpts) ->
|
|
|
- emqx_ds_replication_layer_meta:update_db_config(DB, CreateOpts).
|
|
|
+ ok = emqx_ds_replication_layer_meta:update_db_config(DB, CreateOpts),
|
|
|
+ Opts = emqx_ds_replication_layer_meta:get_options(DB),
|
|
|
+ foreach_shard(
|
|
|
+ DB,
|
|
|
+ fun(Shard) -> ok = ra_update_config(DB, Shard, Opts) end
|
|
|
+ ).
|
|
|
|
|
|
-spec list_generations_with_lifetimes(emqx_ds:db()) ->
|
|
|
#{generation_rank() => emqx_ds:generation_info()}.
|
|
|
@@ -164,13 +181,12 @@ list_generations_with_lifetimes(DB) ->
|
|
|
Shards = list_shards(DB),
|
|
|
lists:foldl(
|
|
|
fun(Shard, GensAcc) ->
|
|
|
- Node = node_of_shard(DB, Shard),
|
|
|
maps:fold(
|
|
|
fun(GenId, Data, AccInner) ->
|
|
|
AccInner#{{Shard, GenId} => Data}
|
|
|
end,
|
|
|
GensAcc,
|
|
|
- emqx_ds_proto_v4:list_generations_with_lifetimes(Node, DB, Shard)
|
|
|
+ ra_list_generations_with_lifetimes(DB, Shard)
|
|
|
)
|
|
|
end,
|
|
|
#{},
|
|
|
@@ -179,18 +195,15 @@ list_generations_with_lifetimes(DB) ->
|
|
|
|
|
|
-spec drop_generation(emqx_ds:db(), generation_rank()) -> ok | {error, _}.
|
|
|
drop_generation(DB, {Shard, GenId}) ->
|
|
|
- %% TODO: drop generation in all nodes in the replica set, not only in the leader,
|
|
|
- %% after we have proper replication in place.
|
|
|
- Node = node_of_shard(DB, Shard),
|
|
|
- emqx_ds_proto_v4:drop_generation(Node, DB, Shard, GenId).
|
|
|
+ ra_drop_generation(DB, Shard, GenId).
|
|
|
|
|
|
-spec drop_db(emqx_ds:db()) -> ok | {error, _}.
|
|
|
drop_db(DB) ->
|
|
|
- Nodes = list_nodes(),
|
|
|
- _ = emqx_ds_proto_v4:drop_db(Nodes, DB),
|
|
|
- _ = emqx_ds_replication_layer_meta:drop_db(DB),
|
|
|
- emqx_ds_builtin_sup:stop_db(DB),
|
|
|
- ok.
|
|
|
+ foreach_shard(DB, fun(Shard) ->
|
|
|
+ {ok, _} = ra_drop_shard(DB, Shard)
|
|
|
+ end),
|
|
|
+ _ = emqx_ds_proto_v4:drop_db(list_nodes(), DB),
|
|
|
+ emqx_ds_replication_layer_meta:drop_db(DB).
|
|
|
|
|
|
-spec store_batch(emqx_ds:db(), [emqx_types:message(), ...], emqx_ds:message_store_opts()) ->
|
|
|
emqx_ds:store_batch_result().
|
|
|
@@ -208,10 +221,9 @@ get_streams(DB, TopicFilter, StartTime) ->
|
|
|
Shards = list_shards(DB),
|
|
|
lists:flatmap(
|
|
|
fun(Shard) ->
|
|
|
- Node = node_of_shard(DB, Shard),
|
|
|
Streams =
|
|
|
try
|
|
|
- emqx_ds_proto_v4:get_streams(Node, DB, Shard, TopicFilter, StartTime)
|
|
|
+ ra_get_streams(DB, Shard, TopicFilter, StartTime)
|
|
|
catch
|
|
|
error:{erpc, _} ->
|
|
|
%% TODO: log?
|
|
|
@@ -235,8 +247,7 @@ get_delete_streams(DB, TopicFilter, StartTime) ->
|
|
|
Shards = list_shards(DB),
|
|
|
lists:flatmap(
|
|
|
fun(Shard) ->
|
|
|
- Node = node_of_shard(DB, Shard),
|
|
|
- Streams = emqx_ds_proto_v4:get_delete_streams(Node, DB, Shard, TopicFilter, StartTime),
|
|
|
+ Streams = ra_get_delete_streams(DB, Shard, TopicFilter, StartTime),
|
|
|
lists:map(
|
|
|
fun(StorageLayerStream) ->
|
|
|
?delete_stream(Shard, StorageLayerStream)
|
|
|
@@ -251,8 +262,7 @@ get_delete_streams(DB, TopicFilter, StartTime) ->
|
|
|
emqx_ds:make_iterator_result(iterator()).
|
|
|
make_iterator(DB, Stream, TopicFilter, StartTime) ->
|
|
|
?stream_v2(Shard, StorageStream) = Stream,
|
|
|
- Node = node_of_shard(DB, Shard),
|
|
|
- try emqx_ds_proto_v4:make_iterator(Node, DB, Shard, StorageStream, TopicFilter, StartTime) of
|
|
|
+ try ra_make_iterator(DB, Shard, StorageStream, TopicFilter, StartTime) of
|
|
|
{ok, Iter} ->
|
|
|
{ok, #{?tag => ?IT, ?shard => Shard, ?enc => Iter}};
|
|
|
Error = {error, _, _} ->
|
|
|
@@ -266,12 +276,7 @@ make_iterator(DB, Stream, TopicFilter, StartTime) ->
|
|
|
emqx_ds:make_delete_iterator_result(delete_iterator()).
|
|
|
make_delete_iterator(DB, Stream, TopicFilter, StartTime) ->
|
|
|
?delete_stream(Shard, StorageStream) = Stream,
|
|
|
- Node = node_of_shard(DB, Shard),
|
|
|
- case
|
|
|
- emqx_ds_proto_v4:make_delete_iterator(
|
|
|
- Node, DB, Shard, StorageStream, TopicFilter, StartTime
|
|
|
- )
|
|
|
- of
|
|
|
+ case ra_make_delete_iterator(DB, Shard, StorageStream, TopicFilter, StartTime) of
|
|
|
{ok, Iter} ->
|
|
|
{ok, #{?tag => ?DELETE_IT, ?shard => Shard, ?enc => Iter}};
|
|
|
Err = {error, _} ->
|
|
|
@@ -282,8 +287,7 @@ make_delete_iterator(DB, Stream, TopicFilter, StartTime) ->
|
|
|
emqx_ds:make_iterator_result(iterator()).
|
|
|
update_iterator(DB, OldIter, DSKey) ->
|
|
|
#{?tag := ?IT, ?shard := Shard, ?enc := StorageIter} = OldIter,
|
|
|
- Node = node_of_shard(DB, Shard),
|
|
|
- try emqx_ds_proto_v4:update_iterator(Node, DB, Shard, StorageIter, DSKey) of
|
|
|
+ try ra_update_iterator(DB, Shard, StorageIter, DSKey) of
|
|
|
{ok, Iter} ->
|
|
|
{ok, #{?tag => ?IT, ?shard => Shard, ?enc => Iter}};
|
|
|
Error = {error, _, _} ->
|
|
|
@@ -296,7 +300,6 @@ update_iterator(DB, OldIter, DSKey) ->
|
|
|
-spec next(emqx_ds:db(), iterator(), pos_integer()) -> emqx_ds:next_result(iterator()).
|
|
|
next(DB, Iter0, BatchSize) ->
|
|
|
#{?tag := ?IT, ?shard := Shard, ?enc := StorageIter0} = Iter0,
|
|
|
- Node = node_of_shard(DB, Shard),
|
|
|
%% TODO: iterator can contain information that is useful for
|
|
|
%% reconstructing messages sent over the network. For example,
|
|
|
%% when we send messages with the learned topic index, we could
|
|
|
@@ -305,7 +308,7 @@ next(DB, Iter0, BatchSize) ->
|
|
|
%%
|
|
|
%% This kind of trickery should be probably done here in the
|
|
|
%% replication layer. Or, perhaps, in the logic layer.
|
|
|
- case emqx_ds_proto_v4:next(Node, DB, Shard, StorageIter0, BatchSize) of
|
|
|
+ case ra_next(DB, Shard, StorageIter0, BatchSize) of
|
|
|
{ok, StorageIter, Batch} ->
|
|
|
Iter = Iter0#{?enc := StorageIter},
|
|
|
{ok, Iter, Batch};
|
|
|
@@ -321,8 +324,7 @@ next(DB, Iter0, BatchSize) ->
|
|
|
emqx_ds:delete_next_result(delete_iterator()).
|
|
|
delete_next(DB, Iter0, Selector, BatchSize) ->
|
|
|
#{?tag := ?DELETE_IT, ?shard := Shard, ?enc := StorageIter0} = Iter0,
|
|
|
- Node = node_of_shard(DB, Shard),
|
|
|
- case emqx_ds_proto_v4:delete_next(Node, DB, Shard, StorageIter0, Selector, BatchSize) of
|
|
|
+ case ra_delete_next(DB, Shard, StorageIter0, Selector, BatchSize) of
|
|
|
{ok, StorageIter, NumDeleted} ->
|
|
|
Iter = Iter0#{?enc := StorageIter},
|
|
|
{ok, Iter, NumDeleted};
|
|
|
@@ -330,21 +332,10 @@ delete_next(DB, Iter0, Selector, BatchSize) ->
|
|
|
Other
|
|
|
end.
|
|
|
|
|
|
--spec node_of_shard(emqx_ds:db(), shard_id()) -> node().
|
|
|
-node_of_shard(DB, Shard) ->
|
|
|
- case emqx_ds_replication_layer_meta:shard_leader(DB, Shard) of
|
|
|
- {ok, Leader} ->
|
|
|
- Leader;
|
|
|
- {error, no_leader_for_shard} ->
|
|
|
- %% TODO: use optvar
|
|
|
- timer:sleep(500),
|
|
|
- node_of_shard(DB, Shard)
|
|
|
- end.
|
|
|
-
|
|
|
-spec shard_of_message(emqx_ds:db(), emqx_types:message(), clientid | topic) ->
|
|
|
emqx_ds_replication_layer:shard_id().
|
|
|
shard_of_message(DB, #message{from = From, topic = Topic}, SerializeBy) ->
|
|
|
- N = emqx_ds_replication_layer_meta:n_shards(DB),
|
|
|
+ N = emqx_ds_replication_shard_allocator:n_shards(DB),
|
|
|
Hash =
|
|
|
case SerializeBy of
|
|
|
clientid -> erlang:phash2(From, N);
|
|
|
@@ -352,18 +343,8 @@ shard_of_message(DB, #message{from = From, topic = Topic}, SerializeBy) ->
|
|
|
end,
|
|
|
integer_to_binary(Hash).
|
|
|
|
|
|
-%% TODO: there's no real leader election right now
|
|
|
--spec maybe_set_myself_as_leader(emqx_ds:db(), shard_id()) -> ok.
|
|
|
-maybe_set_myself_as_leader(DB, Shard) ->
|
|
|
- Site = emqx_ds_replication_layer_meta:this_site(),
|
|
|
- case emqx_ds_replication_layer_meta:in_sync_replicas(DB, Shard) of
|
|
|
- [Site | _] ->
|
|
|
- %% Currently the first in-sync replica always becomes the
|
|
|
- %% leader
|
|
|
- ok = emqx_ds_replication_layer_meta:set_leader(DB, Shard, node());
|
|
|
- _Sites ->
|
|
|
- ok
|
|
|
- end.
|
|
|
+foreach_shard(DB, Fun) ->
|
|
|
+ lists:foreach(Fun, list_shards(DB)).
|
|
|
|
|
|
%%================================================================================
|
|
|
%% behavior callbacks
|
|
|
@@ -392,7 +373,8 @@ do_drop_db_v1(DB) ->
|
|
|
) ->
|
|
|
emqx_ds:store_batch_result().
|
|
|
do_store_batch_v1(DB, Shard, #{?tag := ?BATCH, ?batch_messages := Messages}, Options) ->
|
|
|
- emqx_ds_storage_layer:store_batch({DB, Shard}, Messages, Options).
|
|
|
+ Batch = [{emqx_message:timestamp(Message), Message} || Message <- Messages],
|
|
|
+ emqx_ds_storage_layer:store_batch({DB, Shard}, Batch, Options).
|
|
|
|
|
|
%% Remove me in EMQX 5.6
|
|
|
-dialyzer({nowarn_function, do_get_streams_v1/4}).
|
|
|
@@ -477,15 +459,9 @@ do_next_v1(DB, Shard, Iter, BatchSize) ->
|
|
|
do_delete_next_v4(DB, Shard, Iter, Selector, BatchSize) ->
|
|
|
emqx_ds_storage_layer:delete_next({DB, Shard}, Iter, Selector, BatchSize).
|
|
|
|
|
|
--spec do_add_generation_v2(emqx_ds:db()) -> ok | {error, _}.
|
|
|
-do_add_generation_v2(DB) ->
|
|
|
- MyShards = emqx_ds_replication_layer_meta:my_owned_shards(DB),
|
|
|
- lists:foreach(
|
|
|
- fun(ShardId) ->
|
|
|
- emqx_ds_storage_layer:add_generation({DB, ShardId})
|
|
|
- end,
|
|
|
- MyShards
|
|
|
- ).
|
|
|
+-spec do_add_generation_v2(emqx_ds:db()) -> no_return().
|
|
|
+do_add_generation_v2(_DB) ->
|
|
|
+ error(obsolete_api).
|
|
|
|
|
|
-spec do_list_generations_with_lifetimes_v3(emqx_ds:db(), shard_id()) ->
|
|
|
#{emqx_ds:ds_specific_generation_rank() => emqx_ds:generation_info()}.
|
|
|
@@ -510,3 +486,188 @@ do_get_delete_streams_v4(DB, Shard, TopicFilter, StartTime) ->
|
|
|
|
|
|
list_nodes() ->
|
|
|
mria:running_nodes().
|
|
|
+
|
|
|
+%% TODO
|
|
|
+%% Too large for normal operation, need better backpressure mechanism.
|
|
|
+-define(RA_TIMEOUT, 60 * 1000).
|
|
|
+
|
|
|
+ra_store_batch(DB, Shard, Messages) ->
|
|
|
+ Command = #{
|
|
|
+ ?tag => ?BATCH,
|
|
|
+ ?batch_messages => Messages
|
|
|
+ },
|
|
|
+ Servers = emqx_ds_replication_layer_shard:servers(DB, Shard, leader_preferred),
|
|
|
+ case ra:process_command(Servers, Command, ?RA_TIMEOUT) of
|
|
|
+ {ok, Result, _Leader} ->
|
|
|
+ Result;
|
|
|
+ Error ->
|
|
|
+ Error
|
|
|
+ end.
|
|
|
+
|
|
|
+ra_add_generation(DB, Shard) ->
|
|
|
+ Command = #{
|
|
|
+ ?tag => add_generation,
|
|
|
+ ?since => emqx_ds:timestamp_us()
|
|
|
+ },
|
|
|
+ Servers = emqx_ds_replication_layer_shard:servers(DB, Shard, leader_preferred),
|
|
|
+ case ra:process_command(Servers, Command, ?RA_TIMEOUT) of
|
|
|
+ {ok, Result, _Leader} ->
|
|
|
+ Result;
|
|
|
+ Error ->
|
|
|
+ error(Error, [DB, Shard])
|
|
|
+ end.
|
|
|
+
|
|
|
+ra_update_config(DB, Shard, Opts) ->
|
|
|
+ Command = #{
|
|
|
+ ?tag => update_config,
|
|
|
+ ?config => Opts,
|
|
|
+ ?since => emqx_ds:timestamp_us()
|
|
|
+ },
|
|
|
+ Servers = emqx_ds_replication_layer_shard:servers(DB, Shard, leader_preferred),
|
|
|
+ case ra:process_command(Servers, Command, ?RA_TIMEOUT) of
|
|
|
+ {ok, Result, _Leader} ->
|
|
|
+ Result;
|
|
|
+ Error ->
|
|
|
+ error(Error, [DB, Shard])
|
|
|
+ end.
|
|
|
+
|
|
|
+ra_drop_generation(DB, Shard, GenId) ->
|
|
|
+ Command = #{?tag => drop_generation, ?generation => GenId},
|
|
|
+ Servers = emqx_ds_replication_layer_shard:servers(DB, Shard, leader_preferred),
|
|
|
+ case ra:process_command(Servers, Command, ?RA_TIMEOUT) of
|
|
|
+ {ok, Result, _Leader} ->
|
|
|
+ Result;
|
|
|
+ Error ->
|
|
|
+ error(Error, [DB, Shard])
|
|
|
+ end.
|
|
|
+
|
|
|
+ra_get_streams(DB, Shard, TopicFilter, Time) ->
|
|
|
+ {_, Node} = emqx_ds_replication_layer_shard:server(DB, Shard, local_preferred),
|
|
|
+ TimestampUs = timestamp_to_timeus(Time),
|
|
|
+ emqx_ds_proto_v4:get_streams(Node, DB, Shard, TopicFilter, TimestampUs).
|
|
|
+
|
|
|
+ra_get_delete_streams(DB, Shard, TopicFilter, Time) ->
|
|
|
+ {_Name, Node} = emqx_ds_replication_layer_shard:server(DB, Shard, local_preferred),
|
|
|
+ emqx_ds_proto_v4:get_delete_streams(Node, DB, Shard, TopicFilter, Time).
|
|
|
+
|
|
|
+ra_make_iterator(DB, Shard, Stream, TopicFilter, StartTime) ->
|
|
|
+ {_, Node} = emqx_ds_replication_layer_shard:server(DB, Shard, local_preferred),
|
|
|
+ TimestampUs = timestamp_to_timeus(StartTime),
|
|
|
+ emqx_ds_proto_v4:make_iterator(Node, DB, Shard, Stream, TopicFilter, TimestampUs).
|
|
|
+
|
|
|
+ra_make_delete_iterator(DB, Shard, Stream, TopicFilter, StartTime) ->
|
|
|
+ {_Name, Node} = emqx_ds_replication_layer_shard:server(DB, Shard, local_preferred),
|
|
|
+ emqx_ds_proto_v4:make_delete_iterator(Node, DB, Shard, Stream, TopicFilter, StartTime).
|
|
|
+
|
|
|
+ra_update_iterator(DB, Shard, Iter, DSKey) ->
|
|
|
+ {_Name, Node} = emqx_ds_replication_layer_shard:server(DB, Shard, local_preferred),
|
|
|
+ emqx_ds_proto_v4:update_iterator(Node, DB, Shard, Iter, DSKey).
|
|
|
+
|
|
|
+ra_next(DB, Shard, Iter, BatchSize) ->
|
|
|
+ {_Name, Node} = emqx_ds_replication_layer_shard:server(DB, Shard, local_preferred),
|
|
|
+ emqx_ds_proto_v4:next(Node, DB, Shard, Iter, BatchSize).
|
|
|
+
|
|
|
+ra_delete_next(DB, Shard, Iter, Selector, BatchSize) ->
|
|
|
+ {_Name, Node} = emqx_ds_replication_layer_shard:server(DB, Shard, local_preferred),
|
|
|
+ emqx_ds_proto_v4:delete_next(Node, DB, Shard, Iter, Selector, BatchSize).
|
|
|
+
|
|
|
+ra_list_generations_with_lifetimes(DB, Shard) ->
|
|
|
+ {_Name, Node} = emqx_ds_replication_layer_shard:server(DB, Shard, local_preferred),
|
|
|
+ Gens = emqx_ds_proto_v4:list_generations_with_lifetimes(Node, DB, Shard),
|
|
|
+ maps:map(
|
|
|
+ fun(_GenId, Data = #{since := Since, until := Until}) ->
|
|
|
+ Data#{
|
|
|
+ since := timeus_to_timestamp(Since),
|
|
|
+ until := emqx_maybe:apply(fun timeus_to_timestamp/1, Until)
|
|
|
+ }
|
|
|
+ end,
|
|
|
+ Gens
|
|
|
+ ).
|
|
|
+
|
|
|
+ra_drop_shard(DB, Shard) ->
|
|
|
+ ra:delete_cluster(emqx_ds_replication_layer_shard:shard_servers(DB, Shard), ?RA_TIMEOUT).
|
|
|
+
|
|
|
+%%
|
|
|
+
|
|
|
+init(#{db := DB, shard := Shard}) ->
|
|
|
+ #{db_shard => {DB, Shard}, latest => 0}.
|
|
|
+
|
|
|
+apply(
|
|
|
+ #{index := RaftIdx},
|
|
|
+ #{
|
|
|
+ ?tag := ?BATCH,
|
|
|
+ ?batch_messages := MessagesIn
|
|
|
+ },
|
|
|
+ #{db_shard := DBShard, latest := Latest} = State
|
|
|
+) ->
|
|
|
+ %% NOTE
|
|
|
+ %% Unique timestamp tracking real time closely.
|
|
|
+ %% With microsecond granularity it should be nearly impossible for it to run
|
|
|
+ %% too far ahead than the real time clock.
|
|
|
+ {NLatest, Messages} = assign_timestamps(Latest, MessagesIn),
|
|
|
+ %% TODO
|
|
|
+ %% Batch is now reversed, but it should not make a lot of difference.
|
|
|
+ %% Even if it would be in order, it's still possible to write messages far away
|
|
|
+ %% in the past, i.e. when replica catches up with the leader. Storage layer
|
|
|
+ %% currently relies on wall clock time to decide if it's safe to iterate over
|
|
|
+ %% next epoch, this is likely wrong. Ideally it should rely on consensus clock
|
|
|
+ %% time instead.
|
|
|
+ Result = emqx_ds_storage_layer:store_batch(DBShard, Messages, #{}),
|
|
|
+ NState = State#{latest := NLatest},
|
|
|
+ %% TODO: Need to measure effects of changing frequency of `release_cursor`.
|
|
|
+ Effect = {release_cursor, RaftIdx, NState},
|
|
|
+ {NState, Result, Effect};
|
|
|
+apply(
|
|
|
+ _RaftMeta,
|
|
|
+ #{?tag := add_generation, ?since := Since},
|
|
|
+ #{db_shard := DBShard, latest := Latest} = State
|
|
|
+) ->
|
|
|
+ {Timestamp, NLatest} = ensure_monotonic_timestamp(Since, Latest),
|
|
|
+ Result = emqx_ds_storage_layer:add_generation(DBShard, Timestamp),
|
|
|
+ NState = State#{latest := NLatest},
|
|
|
+ {NState, Result};
|
|
|
+apply(
|
|
|
+ _RaftMeta,
|
|
|
+ #{?tag := update_config, ?since := Since, ?config := Opts},
|
|
|
+ #{db_shard := DBShard, latest := Latest} = State
|
|
|
+) ->
|
|
|
+ {Timestamp, NLatest} = ensure_monotonic_timestamp(Since, Latest),
|
|
|
+ Result = emqx_ds_storage_layer:update_config(DBShard, Timestamp, Opts),
|
|
|
+ NState = State#{latest := NLatest},
|
|
|
+ {NState, Result};
|
|
|
+apply(
|
|
|
+ _RaftMeta,
|
|
|
+ #{?tag := drop_generation, ?generation := GenId},
|
|
|
+ #{db_shard := DBShard} = State
|
|
|
+) ->
|
|
|
+ Result = emqx_ds_storage_layer:drop_generation(DBShard, GenId),
|
|
|
+ {State, Result}.
|
|
|
+
|
|
|
+assign_timestamps(Latest, Messages) ->
|
|
|
+ assign_timestamps(Latest, Messages, []).
|
|
|
+
|
|
|
+assign_timestamps(Latest, [MessageIn | Rest], Acc) ->
|
|
|
+ case emqx_message:timestamp(MessageIn, microsecond) of
|
|
|
+ TimestampUs when TimestampUs > Latest ->
|
|
|
+ Message = assign_timestamp(TimestampUs, MessageIn),
|
|
|
+ assign_timestamps(TimestampUs, Rest, [Message | Acc]);
|
|
|
+ _Earlier ->
|
|
|
+ Message = assign_timestamp(Latest + 1, MessageIn),
|
|
|
+ assign_timestamps(Latest + 1, Rest, [Message | Acc])
|
|
|
+ end;
|
|
|
+assign_timestamps(Latest, [], Acc) ->
|
|
|
+ {Latest, Acc}.
|
|
|
+
|
|
|
+assign_timestamp(TimestampUs, Message) ->
|
|
|
+ {TimestampUs, Message}.
|
|
|
+
|
|
|
+ensure_monotonic_timestamp(TimestampUs, Latest) when TimestampUs > Latest ->
|
|
|
+ {TimestampUs, TimestampUs + 1};
|
|
|
+ensure_monotonic_timestamp(_TimestampUs, Latest) ->
|
|
|
+ {Latest, Latest + 1}.
|
|
|
+
|
|
|
+timestamp_to_timeus(TimestampMs) ->
|
|
|
+ TimestampMs * 1000.
|
|
|
+
|
|
|
+timeus_to_timestamp(TimestampUs) ->
|
|
|
+ TimestampUs div 1000.
|