|
|
@@ -21,20 +21,26 @@
|
|
|
%% implementation details from this module.
|
|
|
-module(emqx_ds_replication_layer_meta).
|
|
|
|
|
|
+-compile(inline).
|
|
|
+
|
|
|
-behaviour(gen_server).
|
|
|
|
|
|
%% API:
|
|
|
-export([
|
|
|
shards/1,
|
|
|
my_shards/1,
|
|
|
+ my_owned_shards/1,
|
|
|
+ leader_nodes/1,
|
|
|
replica_set/2,
|
|
|
in_sync_replicas/2,
|
|
|
sites/0,
|
|
|
open_db/2,
|
|
|
+ update_db_config/2,
|
|
|
drop_db/1,
|
|
|
shard_leader/2,
|
|
|
this_site/0,
|
|
|
set_leader/3,
|
|
|
+ is_leader/1,
|
|
|
print_status/0
|
|
|
]).
|
|
|
|
|
|
@@ -44,16 +50,19 @@
|
|
|
%% internal exports:
|
|
|
-export([
|
|
|
open_db_trans/2,
|
|
|
+ update_db_config_trans/2,
|
|
|
drop_db_trans/1,
|
|
|
claim_site/2,
|
|
|
in_sync_replicas_trans/2,
|
|
|
set_leader_trans/3,
|
|
|
+ is_leader_trans/1,
|
|
|
n_shards/1
|
|
|
]).
|
|
|
|
|
|
-export_type([site/0]).
|
|
|
|
|
|
-include_lib("stdlib/include/qlc.hrl").
|
|
|
+-include_lib("stdlib/include/ms_transform.hrl").
|
|
|
|
|
|
%%================================================================================
|
|
|
%% Type declarations
|
|
|
@@ -145,22 +154,34 @@ start_link() ->
|
|
|
|
|
|
-spec shards(emqx_ds:db()) -> [emqx_ds_replication_layer:shard_id()].
|
|
|
shards(DB) ->
|
|
|
- eval_qlc(
|
|
|
- qlc:q([Shard || #?SHARD_TAB{shard = {D, Shard}} <- mnesia:table(?SHARD_TAB), D =:= DB])
|
|
|
- ).
|
|
|
+ filter_shards(DB).
|
|
|
|
|
|
-spec my_shards(emqx_ds:db()) -> [emqx_ds_replication_layer:shard_id()].
|
|
|
my_shards(DB) ->
|
|
|
Site = this_site(),
|
|
|
- eval_qlc(
|
|
|
- qlc:q([
|
|
|
- Shard
|
|
|
- || #?SHARD_TAB{shard = {D, Shard}, replica_set = ReplicaSet, in_sync_replicas = InSync} <- mnesia:table(
|
|
|
- ?SHARD_TAB
|
|
|
- ),
|
|
|
- D =:= DB,
|
|
|
- lists:member(Site, ReplicaSet) orelse lists:member(Site, InSync)
|
|
|
- ])
|
|
|
+ filter_shards(DB, fun(#?SHARD_TAB{replica_set = ReplicaSet, in_sync_replicas = InSync}) ->
|
|
|
+ lists:member(Site, ReplicaSet) orelse lists:member(Site, InSync)
|
|
|
+ end).
|
|
|
+
|
|
|
+-spec my_owned_shards(emqx_ds:db()) -> [emqx_ds_replication_layer:shard_id()].
|
|
|
+my_owned_shards(DB) ->
|
|
|
+ Self = node(),
|
|
|
+ filter_shards(DB, fun(#?SHARD_TAB{leader = Leader}) ->
|
|
|
+ Self =:= Leader
|
|
|
+ end).
|
|
|
+
|
|
|
+-spec leader_nodes(emqx_ds:db()) -> [node()].
|
|
|
+leader_nodes(DB) ->
|
|
|
+ lists:uniq(
|
|
|
+ filter_shards(
|
|
|
+ DB,
|
|
|
+ fun(#?SHARD_TAB{leader = Leader}) ->
|
|
|
+ Leader =/= undefined
|
|
|
+ end,
|
|
|
+ fun(#?SHARD_TAB{leader = Leader}) ->
|
|
|
+ Leader
|
|
|
+ end
|
|
|
+ )
|
|
|
).
|
|
|
|
|
|
-spec replica_set(emqx_ds:db(), emqx_ds_replication_layer:shard_id()) ->
|
|
|
@@ -204,12 +225,25 @@ set_leader(DB, Shard, Node) ->
|
|
|
{atomic, _} = mria:transaction(?SHARD, fun ?MODULE:set_leader_trans/3, [DB, Shard, Node]),
|
|
|
ok.
|
|
|
|
|
|
+-spec is_leader(node()) -> boolean().
|
|
|
+is_leader(Node) ->
|
|
|
+ {atomic, Result} = mria:transaction(?SHARD, fun ?MODULE:is_leader_trans/1, [Node]),
|
|
|
+ Result.
|
|
|
+
|
|
|
-spec open_db(emqx_ds:db(), emqx_ds_replication_layer:builtin_db_opts()) ->
|
|
|
emqx_ds_replication_layer:builtin_db_opts().
|
|
|
open_db(DB, DefaultOpts) ->
|
|
|
{atomic, Opts} = mria:transaction(?SHARD, fun ?MODULE:open_db_trans/2, [DB, DefaultOpts]),
|
|
|
Opts.
|
|
|
|
|
|
+-spec update_db_config(emqx_ds:db(), emqx_ds_replication_layer:builtin_db_opts()) ->
|
|
|
+ ok | {error, _}.
|
|
|
+update_db_config(DB, DefaultOpts) ->
|
|
|
+ {atomic, Opts} = mria:transaction(?SHARD, fun ?MODULE:update_db_config_trans/2, [
|
|
|
+ DB, DefaultOpts
|
|
|
+ ]),
|
|
|
+ Opts.
|
|
|
+
|
|
|
-spec drop_db(emqx_ds:db()) -> ok.
|
|
|
drop_db(DB) ->
|
|
|
_ = mria:transaction(?SHARD, fun ?MODULE:drop_db_trans/1, [DB]),
|
|
|
@@ -226,6 +260,7 @@ init([]) ->
|
|
|
logger:set_process_metadata(#{domain => [ds, meta]}),
|
|
|
ensure_tables(),
|
|
|
ensure_site(),
|
|
|
+ {ok, _} = mnesia:subscribe({table, ?META_TAB, detailed}),
|
|
|
S = #s{},
|
|
|
{ok, S}.
|
|
|
|
|
|
@@ -235,6 +270,18 @@ handle_call(_Call, _From, S) ->
|
|
|
handle_cast(_Cast, S) ->
|
|
|
{noreply, S}.
|
|
|
|
|
|
+handle_info(
|
|
|
+ {mnesia_table_event, {write, ?META_TAB, #?META_TAB{db = DB, db_props = Options}, [_], _}}, S
|
|
|
+) ->
|
|
|
+ MyShards = my_owned_shards(DB),
|
|
|
+
|
|
|
+ lists:foreach(
|
|
|
+ fun(ShardId) ->
|
|
|
+ emqx_ds_storage_layer:update_config({DB, ShardId}, Options)
|
|
|
+ end,
|
|
|
+ MyShards
|
|
|
+ ),
|
|
|
+ {noreply, S};
|
|
|
handle_info(_Info, S) ->
|
|
|
{noreply, S}.
|
|
|
|
|
|
@@ -260,6 +307,31 @@ open_db_trans(DB, CreateOpts) ->
|
|
|
Opts
|
|
|
end.
|
|
|
|
|
|
+-spec update_db_config_trans(emqx_ds:db(), emqx_ds_replication_layer:builtin_db_opts()) ->
|
|
|
+ ok | {error, database}.
|
|
|
+update_db_config_trans(DB, CreateOpts) ->
|
|
|
+ case mnesia:wread({?META_TAB, DB}) of
|
|
|
+ [#?META_TAB{db_props = Opts}] ->
|
|
|
+ %% Since this is an update and not a reopen,
|
|
|
+ %% we should keep the shard number and replication factor
|
|
|
+ %% and not create a new shard server
|
|
|
+ #{
|
|
|
+ n_shards := NShards,
|
|
|
+ replication_factor := ReplicationFactor
|
|
|
+ } = Opts,
|
|
|
+
|
|
|
+ mnesia:write(#?META_TAB{
|
|
|
+ db = DB,
|
|
|
+ db_props = CreateOpts#{
|
|
|
+ n_shards := NShards,
|
|
|
+ replication_factor := ReplicationFactor
|
|
|
+ }
|
|
|
+ }),
|
|
|
+ ok;
|
|
|
+ [] ->
|
|
|
+ {error, no_database}
|
|
|
+ end.
|
|
|
+
|
|
|
-spec drop_db_trans(emqx_ds:db()) -> ok.
|
|
|
drop_db_trans(DB) ->
|
|
|
mnesia:delete({?META_TAB, DB}),
|
|
|
@@ -287,6 +359,24 @@ set_leader_trans(DB, Shard, Node) ->
|
|
|
Record = Record0#?SHARD_TAB{leader = Node},
|
|
|
mnesia:write(Record).
|
|
|
|
|
|
+-spec is_leader_trans(node) -> boolean().
|
|
|
+is_leader_trans(Node) ->
|
|
|
+ case
|
|
|
+ mnesia:select(
|
|
|
+ ?SHARD_TAB,
|
|
|
+ ets:fun2ms(fun(#?SHARD_TAB{leader = Leader}) ->
|
|
|
+ Leader =:= Node
|
|
|
+ end),
|
|
|
+ 1,
|
|
|
+ read
|
|
|
+ )
|
|
|
+ of
|
|
|
+ {[_ | _], _Cont} ->
|
|
|
+ true;
|
|
|
+ _ ->
|
|
|
+ false
|
|
|
+ end.
|
|
|
+
|
|
|
%%================================================================================
|
|
|
%% Internal functions
|
|
|
%%================================================================================
|
|
|
@@ -346,7 +436,7 @@ create_shards(DB, NShards, ReplicationFactor) ->
|
|
|
Hashes0 = [{hash(Shard, Site), Site} || Site <- AllSites],
|
|
|
Hashes = lists:sort(Hashes0),
|
|
|
{_, Sites} = lists:unzip(Hashes),
|
|
|
- [First | _] = ReplicaSet = lists:sublist(Sites, 1, ReplicationFactor),
|
|
|
+ [First | ReplicaSet] = lists:sublist(Sites, 1, ReplicationFactor),
|
|
|
Record = #?SHARD_TAB{
|
|
|
shard = {DB, Shard},
|
|
|
replica_set = ReplicaSet,
|
|
|
@@ -369,3 +459,30 @@ eval_qlc(Q) ->
|
|
|
{atomic, Result} = mria:ro_transaction(?SHARD, fun() -> qlc:eval(Q) end),
|
|
|
Result
|
|
|
end.
|
|
|
+
|
|
|
+filter_shards(DB) ->
|
|
|
+ filter_shards(DB, const(true)).
|
|
|
+
|
|
|
+-spec filter_shards(emqx_ds:db(), fun((_) -> boolean())) ->
|
|
|
+ [emqx_ds_replication_layer:shard_id()].
|
|
|
+filter_shards(DB, Predicte) ->
|
|
|
+ filter_shards(DB, Predicte, fun(#?SHARD_TAB{shard = {_, ShardId}}) ->
|
|
|
+ ShardId
|
|
|
+ end).
|
|
|
+
|
|
|
+filter_shards(DB, Predicate, Mapper) ->
|
|
|
+ eval_qlc(
|
|
|
+ qlc:q([
|
|
|
+ Mapper(Shard)
|
|
|
+ || #?SHARD_TAB{shard = {D, _}} = Shard <- mnesia:table(
|
|
|
+ ?SHARD_TAB
|
|
|
+ ),
|
|
|
+ D =:= DB,
|
|
|
+ Predicate(Shard)
|
|
|
+ ])
|
|
|
+ ).
|
|
|
+
|
|
|
+const(Result) ->
|
|
|
+ fun(_) ->
|
|
|
+ Result
|
|
|
+ end.
|