src/infra/infra_mnesia_fragmentation.erl View File
| 1 | %% ===================================================================
|
| 2 | %% EventHub – утилита фрагментации больших таблиц Mnesia
|
| 3 | %% ===================================================================
|
| 4 | -module(infra_mnesia_fragmentation).
|
| 5 |
|
| 22 | %% Пример:
|
| 23 | %% infra_mnesia_fragmentation:fragment_table(event, 4).
|
| 24 | %% -------------------------------------------------------------------
|
| 25 | fragment_table(Table, FragCount) when FragCount > 1 ->
|
| 26 | case mnesia:change_table_frag(Table, {activate, FragCount}) of
|
| 27 | {atomic, ok} ->
|
| 43 | defragment_table(Table) ->
|
| 44 | case mnesia:change_table_frag(Table, deactivate) of
|
| 45 | {atomic, ok} ->
|
| 56 | add_fragment(Table, ExtraFrags) ->
|
| 57 | case mnesia:add_table_fragment(Table, ExtraFrags) of
|
| 58 | {atomic, ok} ->
|
| 70 | try
|
| 71 | IsFrag = mnesia:table_info(Table, frag_property),
|
| 72 | FragCount = case IsFrag of
|
| 76 | io:format("Table ~p fragmentation: ~p fragments~n", [Table, FragCount]),
|
| 77 | FragList = mnesia:table_info(Table, frag_dist),
|
| 78 | io:format("Fragment distribution: ~p~n", [FragList]), |
src/infra/infra_secrets.erl View File
src/infra/infra_sup.erl View File
src/infra/migration_engine.erl View File
| 37 |
|
| 38 | %% @doc Безопасно применить pending-миграции при старте (с global lock в Mnesia).
|
| 39 | -spec ensure_applied() -> ok | {error, term()}.
|
| 119 | Now = calendar:universal_time(),
|
| 120 | case mnesia:sync_transaction(fun() -> try_acquire_lock(Now) end) of
|
| 121 | {atomic, ok} -> ok;
|
| 126 | try_acquire_lock(Now) ->
|
| 127 | case mnesia:read(?TABLE, ?LOCK_VERSION, read) of
|
| 128 | [] ->
|
| 129 | mnesia:write(#schema_migration{version = ?LOCK_VERSION, applied_at = Now}),
|
| 130 | ok;
|
| 133 | true ->
|
| 134 | mnesia:write(#schema_migration{version = ?LOCK_VERSION, applied_at = Now}),
|
| 135 | ok;
|
| 136 | false ->
|
| 137 | mnesia:abort(locked)
|
| 138 | end
|
| 146 | release_lock() ->
|
| 147 | mnesia:sync_transaction(fun() ->
|
| 148 | mnesia:delete({?TABLE, ?LOCK_VERSION})
|
| 149 | end),
|
| 186 | [list_to_atom(V) || #schema_migration{version = V} <-
|
| 187 | mnesia:dirty_match_object(#schema_migration{_ = '_'}),
|
| 188 | not is_lock_version(V)].
|
| 196 | mark_applied(Version) ->
|
| 197 | mnesia:dirty_write(#schema_migration{
|
| 198 | version = atom_to_list(Version),
|
| 202 | unmark_applied(Version) ->
|
| 203 | mnesia:dirty_delete({?TABLE, atom_to_list(Version)}). |
src/infra/node_monitor.erl View File
| 4 | %%% Каждые 5 секунд собирает системные метрики (память, процессы,
|
| 5 | %%% транзакции Mnesia, размеры таблиц, сессии, WebSocket-соединения,
|
| 6 | %%% сетевой ввод/вывод) и сохраняет их в локальную таблицу
|
| 40 | init([]) ->
|
| 41 | mnesia:wait_for_tables([node_metric], 5000),
|
| 42 | % Создаём ETS-таблицу для кеширования предыдущих значений io
|
| 105 | run_queue = safe_run_queue(),
|
| 106 | mnesia_commits = mnesia:system_info(transaction_commits),
|
| 107 | mnesia_failures = mnesia:system_info(transaction_failures),
|
| 108 | table_sizes = table_sizes(),
|
| 117 | %%%-------------------------------------------------------------------
|
| 118 | %%% @doc Возвращает карту `#{TableName => Size}` для всех таблиц Mnesia.
|
| 119 | %%% @end
|
| 122 | table_sizes() ->
|
| 123 | Tables = mnesia:system_info(tables),
|
| 124 | maps:from_list([{Tab, mnesia:table_info(Tab, size)} || Tab <- Tables]).
|
src/infra/stats_collector.erl View File
| 1 | %%%-------------------------------------------------------------------
|
| 2 | %%% @doc Сбор admin-статистики через Mnesia detailed-subscribe.
|
| 3 | %%% ETS держит абсолютные upsert-счётчики и дневные бакеты;
|
| 96 | handle_info(flush, State) ->
|
| 97 | flush_to_mnesia(),
|
| 98 | erlang:send_after(?FLUSH_INTERVAL, self(), flush),
|
| 103 | {noreply, State};
|
| 104 | handle_info({mnesia_table_event, Event}, State) ->
|
| 105 | handle_mnesia_event(Event),
|
| 106 | {noreply, State};
|
| 110 | terminate(_Reason, _State) ->
|
| 111 | flush_to_mnesia(),
|
| 112 | ok.
|
| 123 | do_subscribe(State) ->
|
| 124 | load_from_mnesia(),
|
| 125 | case ets:info(?COUNTER_ETS, size) =:= 0 andalso ets:info(?DAILY_ETS, size) =:= 0 of
|
| 129 | lists:foreach(fun(Table) ->
|
| 130 | case lists:member(Table, mnesia:system_info(tables)) of
|
| 131 | true ->
|
| 132 | case mnesia:wait_for_tables([Table], 60000) of
|
| 133 | ok -> mnesia:subscribe({table, Table, detailed});
|
| 134 | {timeout, _} ->
|
| 149 | stats_tops:rebuild(),
|
| 150 | flush_to_mnesia(),
|
| 151 | ok.
|
| 152 |
|
| 153 | load_from_mnesia() ->
|
| 154 | case lists:member(stats_counter, mnesia:system_info(tables)) of
|
| 155 | true ->
|
| 157 | ets:insert(?COUNTER_ETS, {K, V})
|
| 158 | end, mnesia:dirty_match_object(#stats_counter{_ = '_'}));
|
| 159 | false -> ok
|
| 160 | end,
|
| 161 | case lists:member(stats_daily, mnesia:system_info(tables)) of
|
| 162 | true ->
|
| 164 | ets:insert(?DAILY_ETS, {K, V})
|
| 165 | end, mnesia:dirty_match_object(#stats_daily{_ = '_'}));
|
| 166 | false -> ok
|
| 170 | %%%===================================================================
|
| 171 | %%% Mnesia events
|
| 172 | %%%===================================================================
|
| 173 |
|
| 174 | handle_mnesia_event({write, Table, New, Olds, _ActivityId}) ->
|
| 175 | Old = case Olds of
|
| 179 | process_write(Table, New, Old);
|
| 180 | handle_mnesia_event({delete, _Table, _Key, Olds, _ActivityId}) ->
|
| 181 | lists:foreach(fun(Old) -> process_delete(element(1, Old), Old) end, Olds);
|
| 182 | handle_mnesia_event({delete_object, Table, Old, _Olds, _ActivityId}) ->
|
| 183 | process_delete(Table, Old);
|
| 184 | handle_mnesia_event(_) ->
|
| 185 | ok.
|
| 383 | safe_match(Table, Pattern) ->
|
| 384 | case lists:member(Table, mnesia:system_info(tables)) of
|
| 385 | true ->
|
| 386 | try mnesia:dirty_match_object(Pattern) of
|
| 387 | Rows when is_list(Rows) -> Rows
|
| 552 |
|
| 553 | flush_to_mnesia() ->
|
| 554 | Now = calendar:universal_time(),
|
| 555 | case lists:member(stats_counter, mnesia:system_info(tables)) of
|
| 556 | true ->
|
| 557 | lists:foreach(fun({Key, Value}) ->
|
| 558 | mnesia:dirty_write(#stats_counter{key = Key, value = Value, updated_at = Now})
|
| 559 | end, ets:tab2list(?COUNTER_ETS));
|
| 561 | end,
|
| 562 | case lists:member(stats_daily, mnesia:system_info(tables)) of
|
| 563 | true ->
|
| 564 | lists:foreach(fun({Key, Value}) ->
|
| 565 | mnesia:dirty_write(#stats_daily{key = Key, value = Value, updated_at = Now})
|
| 566 | end, ets:tab2list(?DAILY_ETS));
|
| 571 | cleanup_old_daily(RetentionDays) ->
|
| 572 | case lists:member(stats_daily, mnesia:system_info(tables)) of
|
| 573 | false -> ok;
|
| 580 | (#stats_daily{key = {_, Day}} = Row) when Day < Cutoff ->
|
| 581 | mnesia:dirty_delete_object(Row),
|
| 582 | ets:delete(?DAILY_ETS, Row#stats_daily.key);
|
| 583 | (_) -> ok
|
| 584 | end, mnesia:dirty_match_object(#stats_daily{_ = '_'})),
|
| 585 | ok |
src/infra/stats_tops.erl View File
| 82 | foreach_table(Table, Pattern, Fun) ->
|
| 83 | case lists:member(Table, mnesia:system_info(tables)) of
|
| 84 | true ->
|
| 85 | try lists:foreach(Fun, mnesia:dirty_match_object(Pattern))
|
| 86 | catch
|
| 322 | load_event(Id) when is_binary(Id) ->
|
| 323 | case mnesia:dirty_read({event, Id}) of
|
| 324 | [E] -> {true, E};
|
| 329 | load_calendar(Id) when is_binary(Id) ->
|
| 330 | case mnesia:dirty_read({calendar, Id}) of
|
| 331 | [C] -> {true, C}; |
src/logic/logic_event.erl View File
| 131 | },
|
| 132 | mnesia:dirty_write(Exception),
|
| 133 | {ok, cancelled};
|
| 257 | % Для конкретного календаря загружаем все события (любой статус)
|
| 258 | mnesia:dirty_index_match_object(
|
| 259 | event,
|
| 393 | Match = #recurrence_exception{master_id = MasterId, _ = '_'},
|
| 394 | mnesia:dirty_match_object(Match).
|
| 395 |
|
| 401 | merge_materialized(MasterId, Occurrences) ->
|
| 402 | Materialized = mnesia:dirty_match_object(
|
| 403 | #event{master_id = MasterId, is_instance = true, status = active, _ = '_'} |
src/logic/logic_review.erl View File
| 310 | %%% записана во все изменённые отзывы. Все изменения выполняются
|
| 311 | %%% в одной Mnesia‑транзакции.
|
| 312 | %%%
|
| 332 | end,
|
| 333 | case mnesia:transaction(Fun) of
|
| 334 | {atomic, ok} ->
|
| 347 | {ok, _} -> ok;
|
| 348 | {error, _} -> mnesia:abort(<<"review_update_failed">>)
|
| 349 | end;
|
| 350 | not_found ->
|
| 351 | mnesia:abort(<<"review_not_found">>)
|
| 352 | end. |