Настройка хранения и создание индексов #13
This commit is contained in:
@@ -18,17 +18,11 @@ create(Email, Password) ->
|
|||||||
Id = generate_id(),
|
Id = generate_id(),
|
||||||
{ok, PasswordHash} = logic_auth:hash_password(Password),
|
{ok, PasswordHash} = logic_auth:hash_password(Password),
|
||||||
|
|
||||||
% Определяем роль: первый пользователь становится админом
|
|
||||||
Role = case mnesia:dirty_match_object(#user{_ = '_'}) of
|
|
||||||
[] -> admin;
|
|
||||||
_ -> user
|
|
||||||
end,
|
|
||||||
|
|
||||||
User = #user{
|
User = #user{
|
||||||
id = Id,
|
id = Id,
|
||||||
email = Email,
|
email = Email,
|
||||||
password_hash = PasswordHash,
|
password_hash = PasswordHash,
|
||||||
role = Role,
|
role = user,
|
||||||
status = active,
|
status = active,
|
||||||
created_at = calendar:universal_time(),
|
created_at = calendar:universal_time(),
|
||||||
updated_at = calendar:universal_time()
|
updated_at = calendar:universal_time()
|
||||||
|
|||||||
@@ -1,17 +1,31 @@
|
|||||||
|
%% ===================================================================
|
||||||
|
%% EventHub – infra_mnesia (стабильная версия с автоочисткой при fresh старте)
|
||||||
|
%% ===================================================================
|
||||||
-module(infra_mnesia).
|
-module(infra_mnesia).
|
||||||
-behaviour(gen_server).
|
-behaviour(gen_server).
|
||||||
|
|
||||||
-include("records.hrl").
|
-include("records.hrl").
|
||||||
|
|
||||||
%% API
|
|
||||||
-export([start_link/0, init_tables/0, wait_for_tables/0]).
|
-export([start_link/0, init_tables/0, wait_for_tables/0]).
|
||||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
|
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||||
|
terminate/2, code_change/3]).
|
||||||
|
|
||||||
-define(TABLES, [
|
-define(TABLES, [
|
||||||
user, session, admin, admin_session, calendar, calendar_share, event, recurrence_exception,
|
user, session, admin, admin_session,
|
||||||
booking, review, report, banned_word, ticket, subscription, admin_audit
|
calendar, calendar_share, calendar_specialist,
|
||||||
|
event, recurrence_exception,
|
||||||
|
booking,
|
||||||
|
review, report, banned_word,
|
||||||
|
ticket, subscription,
|
||||||
|
admin_audit, notification
|
||||||
]).
|
]).
|
||||||
|
|
||||||
|
-define(TABLE_WAIT_TIMEOUT, 5000).
|
||||||
|
|
||||||
|
%% ===================================================================
|
||||||
|
%% API
|
||||||
|
%% ===================================================================
|
||||||
|
|
||||||
start_link() ->
|
start_link() ->
|
||||||
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
|
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
|
||||||
|
|
||||||
@@ -21,125 +35,123 @@ init_tables() ->
|
|||||||
wait_for_tables() ->
|
wait_for_tables() ->
|
||||||
gen_server:call(?MODULE, wait_for_tables).
|
gen_server:call(?MODULE, wait_for_tables).
|
||||||
|
|
||||||
|
%% ===================================================================
|
||||||
|
%% gen_server callbacks
|
||||||
|
%% ===================================================================
|
||||||
|
|
||||||
init([]) ->
|
init([]) ->
|
||||||
{ok, #{}}.
|
{ok, #{}}.
|
||||||
|
|
||||||
handle_call(init_tables, _From, State) ->
|
handle_call(init_tables, _From, State) ->
|
||||||
ok = ensure_schema(),
|
ok = maybe_recreate_schema(),
|
||||||
|
ok = ensure_cluster_join(),
|
||||||
lists:foreach(fun create_table/1, ?TABLES),
|
lists:foreach(fun create_table/1, ?TABLES),
|
||||||
|
ok = create_indices(),
|
||||||
{reply, ok, State};
|
{reply, ok, State};
|
||||||
|
|
||||||
handle_call(wait_for_tables, _From, State) ->
|
handle_call(wait_for_tables, _From, State) ->
|
||||||
mnesia:wait_for_tables(?TABLES, 5000),
|
mnesia:wait_for_tables(?TABLES, ?TABLE_WAIT_TIMEOUT),
|
||||||
{reply, ok, State}.
|
{reply, ok, State}.
|
||||||
|
|
||||||
handle_cast(_Msg, State) ->
|
handle_cast(_Msg, State) -> {noreply, State}.
|
||||||
{noreply, State}.
|
handle_info(_Info, State) -> {noreply, State}.
|
||||||
|
terminate(_Reason, _State) -> ok.
|
||||||
|
code_change(_OldVsn, State, _Extra) -> {ok, State}.
|
||||||
|
|
||||||
handle_info(_Info, State) ->
|
%% ===================================================================
|
||||||
{noreply, State}.
|
%% Проверка директории Mnesia и при необходимости пересоздание схемы
|
||||||
|
%% ===================================================================
|
||||||
|
|
||||||
terminate(_Reason, _State) ->
|
maybe_recreate_schema() ->
|
||||||
ok.
|
MnesiaDir = mnesia:system_info(directory),
|
||||||
|
case filelib:is_dir(MnesiaDir) of
|
||||||
code_change(_OldVsn, State, _Extra) ->
|
false ->
|
||||||
{ok, State}.
|
io:format("Mnesia directory not found. Creating fresh schema...~n"),
|
||||||
|
mnesia:stop(),
|
||||||
%% Internal functions
|
mnesia:delete_schema([node()]),
|
||||||
ensure_schema() ->
|
mnesia:create_schema([node()]),
|
||||||
case mnesia:create_schema([node()]) of
|
mnesia:start(),
|
||||||
ok ->
|
|
||||||
ok;
|
ok;
|
||||||
{error, {Node, {already_exists, Node}}} ->
|
true ->
|
||||||
ok;
|
io:format("Mnesia directory exists (~s). Reusing existing schema.~n", [MnesiaDir]),
|
||||||
{error, {already_exists, _Node}} ->
|
case mnesia:system_info(is_running) of
|
||||||
ok;
|
yes -> ok;
|
||||||
{error, Reason} ->
|
_ -> mnesia:start()
|
||||||
error({schema_creation_failed, Reason})
|
end
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
%% ===================================================================
|
||||||
|
%% Кластер
|
||||||
|
%% ===================================================================
|
||||||
|
|
||||||
|
ensure_cluster_join() ->
|
||||||
|
ExtraNodes = application:get_env(eventhub, extra_db_nodes, []),
|
||||||
|
case ExtraNodes of
|
||||||
|
[] -> ok;
|
||||||
|
Nodes ->
|
||||||
|
ok = mnesia:change_config(extra_db_nodes, Nodes),
|
||||||
|
case lists:member(node(), mnesia:table_info(schema, disc_copies)) of
|
||||||
|
false -> mnesia:add_table_copy(schema, node(), disc_copies);
|
||||||
|
true -> ok
|
||||||
|
end
|
||||||
|
end.
|
||||||
|
|
||||||
|
%% ===================================================================
|
||||||
|
%% Создание таблиц
|
||||||
|
%% ===================================================================
|
||||||
|
|
||||||
create_table(Table) ->
|
create_table(Table) ->
|
||||||
case mnesia:create_table(Table, table_opts(Table)) of
|
Opts = table_opts(Table),
|
||||||
{atomic, ok} ->
|
case mnesia:create_table(Table, Opts) of
|
||||||
ok;
|
{atomic, ok} -> ok;
|
||||||
{aborted, {already_exists, _}} ->
|
{aborted, {already_exists, _}} -> ok;
|
||||||
ok; % таблица уже существует – пропускаем
|
|
||||||
{aborted, Reason} ->
|
{aborted, Reason} ->
|
||||||
error({table_creation_failed, Table, Reason})
|
error({table_creation_failed, Table, Reason})
|
||||||
end.
|
end.
|
||||||
|
|
||||||
%% Опции таблиц без индексов (добавим позже)
|
%% ===================================================================
|
||||||
table_opts(user) ->
|
%% Опции хранения таблиц
|
||||||
[
|
%% ===================================================================
|
||||||
{attributes, record_info(fields, user)},
|
|
||||||
{ram_copies, [node()]}
|
table_opts(user) -> [{disc_copies, [node()]}, {attributes, record_info(fields, user)}];
|
||||||
];
|
table_opts(admin) -> [{disc_copies, [node()]}, {attributes, record_info(fields, admin)}];
|
||||||
table_opts(session) ->
|
table_opts(calendar) -> [{disc_copies, [node()]}, {attributes, record_info(fields, calendar)}];
|
||||||
[
|
table_opts(calendar_share) -> [{disc_copies, [node()]}, {attributes, record_info(fields, calendar_share)}];
|
||||||
{attributes, record_info(fields, session)},
|
table_opts(calendar_specialist) -> [{disc_copies, [node()]}, {attributes, record_info(fields, calendar_specialist)}];
|
||||||
{ram_copies, [node()]}
|
table_opts(event) -> [{disc_copies, [node()]}, {attributes, record_info(fields, event)}];
|
||||||
];
|
table_opts(recurrence_exception) -> [{disc_copies, [node()]}, {attributes, record_info(fields, recurrence_exception)}];
|
||||||
table_opts(admin) ->
|
table_opts(booking) -> [{disc_copies, [node()]}, {attributes, record_info(fields, booking)}];
|
||||||
[
|
table_opts(review) -> [{disc_copies, [node()]}, {attributes, record_info(fields, review)}];
|
||||||
{attributes, record_info(fields, admin)},
|
table_opts(report) -> [{disc_copies, [node()]}, {attributes, record_info(fields, report)}];
|
||||||
{ram_copies, [node()]}
|
table_opts(banned_word) -> [{disc_copies, [node()]}, {attributes, record_info(fields, banned_word)}];
|
||||||
];
|
table_opts(ticket) -> [{disc_copies, [node()]}, {attributes, record_info(fields, ticket)}];
|
||||||
table_opts(admin_session) ->
|
table_opts(subscription) -> [{disc_copies, [node()]}, {attributes, record_info(fields, subscription)}];
|
||||||
[
|
table_opts(admin_audit) -> [{disc_copies, [node()]}, {attributes, record_info(fields, admin_audit)}];
|
||||||
{attributes, record_info(fields, admin_session)},
|
table_opts(notification) -> [{disc_copies, [node()]}, {attributes, record_info(fields, notification)}];
|
||||||
{ram_copies, [node()]}
|
table_opts(session) -> [{ram_copies, [node()]}, {attributes, record_info(fields, session)}];
|
||||||
];
|
table_opts(admin_session) -> [{ram_copies, [node()]}, {attributes, record_info(fields, admin_session)}].
|
||||||
table_opts(calendar) ->
|
|
||||||
[
|
%% ===================================================================
|
||||||
{attributes, record_info(fields, calendar)},
|
%% Индексы
|
||||||
{ram_copies, [node()]}
|
%% ===================================================================
|
||||||
];
|
|
||||||
table_opts(calendar_share) ->
|
create_indices() ->
|
||||||
[
|
mnesia:add_table_index(event, calendar_id),
|
||||||
{attributes, record_info(fields, calendar_share)},
|
mnesia:add_table_index(event, start_time),
|
||||||
{ram_copies, [node()]}
|
mnesia:add_table_index(event, event_type),
|
||||||
];
|
mnesia:add_table_index(event, master_id),
|
||||||
table_opts(event) ->
|
mnesia:add_table_index(event, specialist_id),
|
||||||
[
|
mnesia:add_table_index(event, status),
|
||||||
{attributes, record_info(fields, event)},
|
mnesia:add_table_index(booking, event_id),
|
||||||
{ram_copies, [node()]}
|
mnesia:add_table_index(booking, user_id),
|
||||||
];
|
mnesia:add_table_index(booking, status),
|
||||||
table_opts(recurrence_exception) ->
|
mnesia:add_table_index(calendar, owner_id),
|
||||||
[
|
mnesia:add_table_index(calendar, status),
|
||||||
{attributes, record_info(fields, recurrence_exception)},
|
mnesia:add_table_index(calendar, short_name),
|
||||||
{ram_copies, [node()]}
|
mnesia:add_table_index(calendar, category),
|
||||||
];
|
mnesia:add_table_index(calendar_specialist, calendar_id),
|
||||||
table_opts(booking) ->
|
mnesia:add_table_index(calendar_specialist, user_id),
|
||||||
[
|
mnesia:add_table_index(user, nickname),
|
||||||
{attributes, record_info(fields, booking)},
|
mnesia:add_table_index(notification, user_id),
|
||||||
{ram_copies, [node()]}
|
mnesia:add_table_index(notification, is_read),
|
||||||
];
|
ok.
|
||||||
table_opts(review) ->
|
|
||||||
[
|
|
||||||
{attributes, record_info(fields, review)},
|
|
||||||
{ram_copies, [node()]}
|
|
||||||
];
|
|
||||||
table_opts(report) ->
|
|
||||||
[
|
|
||||||
{attributes, record_info(fields, report)},
|
|
||||||
{ram_copies, [node()]}
|
|
||||||
];
|
|
||||||
table_opts(banned_word) ->
|
|
||||||
[
|
|
||||||
{attributes, record_info(fields, banned_word)},
|
|
||||||
{ram_copies, [node()]}
|
|
||||||
];
|
|
||||||
table_opts(ticket) ->
|
|
||||||
[
|
|
||||||
{attributes, record_info(fields, ticket)},
|
|
||||||
{ram_copies, [node()]}
|
|
||||||
];
|
|
||||||
table_opts(subscription) ->
|
|
||||||
[
|
|
||||||
{attributes, record_info(fields, subscription)},
|
|
||||||
{ram_copies, [node()]}
|
|
||||||
];
|
|
||||||
table_opts(admin_audit) ->
|
|
||||||
[
|
|
||||||
{attributes, record_info(fields, admin_audit)},
|
|
||||||
{ram_copies, [node()]}
|
|
||||||
].
|
|
||||||
84
src/infra/infra_mnesia_fragmentation.erl
Normal file
84
src/infra/infra_mnesia_fragmentation.erl
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
%% ===================================================================
|
||||||
|
%% EventHub – утилита фрагментации больших таблиц Mnesia
|
||||||
|
%% ===================================================================
|
||||||
|
-module(infra_mnesia_fragmentation).
|
||||||
|
|
||||||
|
-export([
|
||||||
|
fragment_table/2,
|
||||||
|
defragment_table/1,
|
||||||
|
add_fragment/2,
|
||||||
|
info/1
|
||||||
|
]).
|
||||||
|
|
||||||
|
%% -------------------------------------------------------------------
|
||||||
|
%% @doc Включает фрагментацию для заданной таблицы.
|
||||||
|
%% Table - имя таблицы (atom)
|
||||||
|
%% FragCount - количество фрагментов (integer > 1)
|
||||||
|
%%
|
||||||
|
%% После вызова таблица будет автоматически распределена
|
||||||
|
%% по фрагментам. Новые записи будут попадать в нужный фрагмент
|
||||||
|
%% на основе хеша ключа.
|
||||||
|
%%
|
||||||
|
%% Пример:
|
||||||
|
%% infra_mnesia_fragmentation:fragment_table(event, 4).
|
||||||
|
%% -------------------------------------------------------------------
|
||||||
|
fragment_table(Table, FragCount) when FragCount > 1 ->
|
||||||
|
case mnesia:change_table_frag(Table, {activate, FragCount}) of
|
||||||
|
{atomic, ok} ->
|
||||||
|
io:format("Table ~p successfully fragmented into ~p fragments~n", [Table, FragCount]),
|
||||||
|
ok;
|
||||||
|
{aborted, Reason} ->
|
||||||
|
{error, {fragmentation_failed, Table, Reason}}
|
||||||
|
end;
|
||||||
|
|
||||||
|
fragment_table(_Table, FragCount) ->
|
||||||
|
{error, {invalid_frag_count, FragCount}}.
|
||||||
|
|
||||||
|
%% -------------------------------------------------------------------
|
||||||
|
%% @doc Отключает фрагментацию, возвращая таблицу к обычному виду.
|
||||||
|
%% Все данные остаются сохранными, но таблица перестаёт быть
|
||||||
|
%% фрагментированной. Может занять продолжительное время
|
||||||
|
%% на больших объёмах.
|
||||||
|
%% -------------------------------------------------------------------
|
||||||
|
defragment_table(Table) ->
|
||||||
|
case mnesia:change_table_frag(Table, deactivate) of
|
||||||
|
{atomic, ok} ->
|
||||||
|
io:format("Fragmentation removed for table ~p~n", [Table]),
|
||||||
|
ok;
|
||||||
|
{aborted, Reason} ->
|
||||||
|
{error, {defragmentation_failed, Table, Reason}}
|
||||||
|
end.
|
||||||
|
|
||||||
|
%% -------------------------------------------------------------------
|
||||||
|
%% @doc Добавляет новый фрагмент к уже фрагментированной таблице.
|
||||||
|
%% Полезно для постепенного масштабирования.
|
||||||
|
%% -------------------------------------------------------------------
|
||||||
|
add_fragment(Table, ExtraFrags) ->
|
||||||
|
case mnesia:add_table_fragment(Table, ExtraFrags) of
|
||||||
|
{atomic, ok} ->
|
||||||
|
io:format("Added ~p fragment(s) to table ~p~n", [ExtraFrags, Table]),
|
||||||
|
ok;
|
||||||
|
{aborted, Reason} ->
|
||||||
|
{error, {add_fragment_failed, Table, Reason}}
|
||||||
|
end.
|
||||||
|
|
||||||
|
%% -------------------------------------------------------------------
|
||||||
|
%% @doc Выводит информацию о фрагментации таблицы (если включена)
|
||||||
|
%% или о текущем состоянии.
|
||||||
|
%% -------------------------------------------------------------------
|
||||||
|
info(Table) ->
|
||||||
|
try
|
||||||
|
IsFrag = mnesia:table_info(Table, frag_property),
|
||||||
|
FragCount = case IsFrag of
|
||||||
|
[{n_fragments, N} | _] -> N;
|
||||||
|
_ -> 1
|
||||||
|
end,
|
||||||
|
io:format("Table ~p fragmentation: ~p fragments~n", [Table, FragCount]),
|
||||||
|
FragList = mnesia:table_info(Table, frag_dist),
|
||||||
|
io:format("Fragment distribution: ~p~n", [FragList]),
|
||||||
|
ok
|
||||||
|
catch
|
||||||
|
_:_ ->
|
||||||
|
io:format("Table ~p is not fragmented~n", [Table]),
|
||||||
|
ok
|
||||||
|
end.
|
||||||
Reference in New Issue
Block a user