%%% ---------------------------------------------------------------------------- %%% @author Jim Rosenblum %%% @copyright (C) 2015, Jim Rosenblum %%% @doc Either Joins an existing Mnesia cluster, copying tables over to this %%% replica; or create the first node of the cluster and creates relevant %%% tables. Created tables: %%% %%% %%% Desiged to create a diskless cluster (schema is RAM and all tables in RAM) %%% therefore sys.config MUST CONTAIN {schema_location, ram}. %%% @end %%% Created : 17 Mar 2015 by Jim Rosenblum %%% ---------------------------------------------------------------------------- -module(jc_cluster). -compile(nowarn_deprecated_function). % accomidate now() for v < 18 %% API -export([init/0]). % Record and type definitions. -include("../include/records.hrl"). % Time to wait for mnesia tables to initialize when joining cluster. -define(WAIT_FOR_TABLES_MS, 30000). %%% ============================================================================ %%% Library Interface %%% ============================================================================ %% ----------------------------------------------------------------------------- %% @doc Either join an existing Mnesia cluster or start one, creating the tables %% as appropriate. Notice that sys.config MUST contain {schema_location, ram}. %% -spec init() -> ok. init()-> Nodes = application:get_env(jc, cache_nodes, [node()]), [Node || Node <- Nodes, pong == net_adm:ping(Node) ], mnesia:stop(), global:sync(), global:trans({jc_mnesia, self()}, fun() -> mnesia:start(), case [Node || Node <- nodes(), jc_loaded(Node)] of [] -> mnesia:create_schema([]), dynamic_db_init([]), Indexes = application:get_env(jc, indexes, []), [jc_store:start_indexing(Map, Path) || {Map, Path} <- Indexes]; Ns -> dynamic_db_init(Ns) end, true = global:del_lock({jc_mnesia, self()}), ok end, [node() | nodes()], infinity). %%% ============================================================================ %%% Internal functions %%% ============================================================================ %% ----------------------------------------------------------------------------- %% @private Jcache is considered loaded if jc_psub is up - it is the last gen_server %% started by the superviosr. %% -spec jc_loaded(Node::node()) -> true | false. jc_loaded(Node) -> is_pid(rpc:call(Node, erlang, whereis, [jc_psub], 1000)). %% ----------------------------------------------------------------------------- %% @private If there are existing nodes, add this one to the cluster and copy %% the needed tables to this replica. Otherwise, create the tables. %% -spec dynamic_db_init(CacheNodes::[node()] | []) -> ok. dynamic_db_init([]) -> lager:info("Creating cluster"), mnesia:create_table(key_to_value, [{attributes, record_info(fields, key_to_value)}, {type, ordered_set}, {index, [map, key, ref, create_tm]} ]), mnesia:create_table(seq, [{attributes, record_info(fields, seq)}, {type, set} ]), mnesia:create_table(to_index, [{attributes, record_info(fields, to_index)}, {type, set}, {index, [map_name, position]} ]), mnesia:create_table(auto_index, [{attributes, record_info(fields, auto_index)}, {type, set} ]), mnesia:create_table(ttl, [{attributes, record_info(fields, ttl)} ]), mnesia:create_table(max_ttl, [{attributes, record_info(fields, max_ttl)}, {type, set} ]), mnesia:create_table(stats, [{attributes, record_info(fields, stats)} ]), mnesia:create_table(ps_sub, [{attributes, record_info(fields, ps_sub)}, {local_content, true} ]), mnesia:create_table(ps_client, [{attributes, record_info(fields, ps_client)}, {local_content, true} ]), UpRec = #stats{key = 'jc_store_up_time', value = calendar:now_to_datetime(timestamp())}, mnesia:dirty_write(UpRec), ok; dynamic_db_init(CacheNodes) -> add_extra_nodes(CacheNodes). add_extra_nodes([Node|Nds]) -> Wait = application:get_env(jc, table_wait_ms, ?WAIT_FOR_TABLES_MS), case mnesia:change_config(extra_db_nodes, [Node]) of {ok, [Node]} -> lager:info("~p: joining cluster: ~p", [?MODULE, nodes()]), mnesia:add_table_copy(schema, node(), ram_copies), mnesia:add_table_copy(key_to_value, node(), ram_copies), mnesia:add_table_copy(seq, node(), ram_copies), mnesia:add_table_copy(auto_index, node(), ram_copies), mnesia:add_table_copy(to_index, node(), ram_copies), mnesia:add_table_copy(ttl, node(), ram_copies), mnesia:add_table_copy(max_ttl, node(), ram_copies), mnesia:add_table_copy(stats, node(), ram_copies), mnesia:add_table_copy(ps_sub, node(), ram_copies), mnesia:add_table_copy(ps_client, node(), ram_copies), Tables = mnesia:system_info(tables), ok = mnesia:wait_for_tables(Tables, Wait), ok; _ -> add_extra_nodes(Nds) end. % Try to used erlang 18+ timestamp(), support older versions if necessary. timestamp() -> try erlang:timestamp() catch error:undef -> erlang:now() end.