Настройка репликации #14

Closed
opened 2026-05-02 16:15:07 +03:00 by aleksey · 1 comment
Owner

Этап 2. Настройка репликации (пункт 3 задачи)

Цель: обеспечить полную репликацию горячих таблиц на все узлы кластера для отказоустойчивости и балансировки чтения.

Шаги:

  1. Динамическое подключение к кластеру:
    • В infra_mnesia:start/0 обрабатывать параметр extra_db_nodes из конфигурации (например, системная переменная MNESIA_EXTRA_DB_NODES).
    • Если параметр задан, вызвать mnesia:change_config(extra_db_nodes, Nodes) перед созданием таблиц.
  2. Добавление реплик для новых узлов:
    • Определить логику: если таблицы уже существуют на других узлах, не создавать их заново, а добавить локальную реплику: mnesia:add_table_replica(Table, node(), disc_copies).
    • Для этого при старте временно подключиться к одному из существующих узлов, получить список таблиц и их структуру.
  3. Синхронизация схемы:
    • При добавлении совсем нового узла (без данных) скопировать схему с помощью mnesia:change_table_copy_type(schema, node(), disc_copies) или через mnesia:add_table_copy для каждой таблицы.
    • Учесть, что таблицы, созданные с опцией disc_only_copies на других узлах, могут не реплицироваться; их реплицировать не нужно.
  4. Тестирование:
    • Запустить два (или более) узла Erlang, убедиться, что записи появляются на всех узлах, при остановке одного узла данные доступны на другом.
    • Проверить асинхронную репликацию (если будет применяться) — ввести опцию в конфигурации таблиц.

Результат: горячие таблицы реплицированы, кластер устойчив к выходу из строя отдельных узлов.

## Этап 2. Настройка репликации (пункт 3 задачи) **Цель**: обеспечить полную репликацию горячих таблиц на все узлы кластера для отказоустойчивости и балансировки чтения. **Шаги**: 1. **Динамическое подключение к кластеру**: - В `infra_mnesia:start/0` обрабатывать параметр `extra_db_nodes` из конфигурации (например, системная переменная `MNESIA_EXTRA_DB_NODES`). - Если параметр задан, вызвать `mnesia:change_config(extra_db_nodes, Nodes)` перед созданием таблиц. 2. **Добавление реплик для новых узлов**: - Определить логику: если таблицы уже существуют на других узлах, не создавать их заново, а добавить локальную реплику: `mnesia:add_table_replica(Table, node(), disc_copies)`. - Для этого при старте временно подключиться к одному из существующих узлов, получить список таблиц и их структуру. 3. **Синхронизация схемы**: - При добавлении совсем нового узла (без данных) скопировать схему с помощью `mnesia:change_table_copy_type(schema, node(), disc_copies)` или через `mnesia:add_table_copy` для каждой таблицы. - Учесть, что таблицы, созданные с опцией `disc_only_copies` на других узлах, могут не реплицироваться; их реплицировать не нужно. 4. **Тестирование**: - Запустить два (или более) узла Erlang, убедиться, что записи появляются на всех узлах, при остановке одного узла данные доступны на другом. - Проверить асинхронную репликацию (если будет применяться) — ввести опцию в конфигурации таблиц. **Результат**: горячие таблицы реплицированы, кластер устойчив к выходу из строя отдельных узлов.
aleksey added the Future label 2026-05-02 16:15:07 +03:00
Author
Owner

План выполнения задачи #14 — Настройка репликации Mnesia

Текущая ситуация

  • cluster_discovery.erl реализует динамическое обнаружение узлов через DNS-запрос (eventhub-node) и устанавливает Erlang-соединение между узлами (net_kernel:connect_node).
  • infra_mnesia.erl содержит ensure_cluster_join/0, которая добавляет узлы в кластер Mnesia через extra_db_nodes и копирует схему, но не создаёт реплики таблиц с данными.
  • Таким образом, после подключения нового узла данные не реплицируются — узел остаётся пустым.

Цель

Обеспечить автоматическую полную репликацию всех горячих таблиц (user, event, booking, calendar и др.) на каждый новый узел, присоединяющийся к кластеру, а также на существующие узлы при перезапуске.


Шаг 1. Доработка cluster_discovery.erl

Файл: src/infra/cluster_discovery.erl

  • После успешного net_kernel:connect_node(Node) добавить вызов функции, которая выполняет репликацию таблиц Mnesia.
  • Реализовать функцию replicate_tables/0 (или replicate_to_node/1), которая:
    1. Получает список всех таблиц Mnesia (кроме schema) через mnesia:system_info(tables).
    2. Для каждой таблицы, если локальный узел ещё не имеет дисковой копии (disc_copies), вызывает mnesia:add_table_copy(Table, node(), disc_copies).
  • При старте в режимах swarm и remote (eventhub_app.erl) вызывать новую функцию cluster_discovery:discover_and_replicate/0, которая объединяет обнаружение и репликацию.

Примерный код:

discover_and_replicate() ->
    % существующий код обнаружения...
    Nodes = discover_nodes(),
    lists:foreach(fun(Node) ->
        case net_kernel:connect_node(Node) of
            true ->
                replicate_to_node(Node);
            false ->
                io:format("Failed to connect to ~p~n", [Node])
        end
    end, Nodes).

replicate_to_node(Node) ->
    mnesia:change_config(extra_db_nodes, [Node]),
    Tables = mnesia:system_info(tables) -- [schema],
    lists:foreach(fun(Tab) ->
        case lists:member(node(), mnesia:table_info(Tab, disc_copies)) of
            false ->
                mnesia:add_table_copy(Tab, node(), disc_copies);
            true -> ok
        end
    end, Tables). 

Шаг 2. Доработка ensure_cluster_join/0 в infra_mnesia.erl

Цель

При запуске узла, которому в конфигурации заданы узлы кластера через extra_db_nodes, автоматически создавать локальные дисковые копии всех таблиц, чтобы новый узел становился полноценной репликой данных, а не только подключался к схеме.

Текущее состояние

Функция ensure_cluster_join/0 в src/infra/infra_mnesia.erl вызывает mnesia:change_config(extra_db_nodes, Nodes) и, при необходимости, добавляет копию схемы (mnesia:add_table_copy(schema, node(), disc_copies)). Однако реплики остальных таблиц не создаются, поэтому данные на новом узле остаются недоступными до первого обращения (и то не полностью).

Необходимые изменения

  1. После успешного подключения к кластеру получить список всех таблиц Mnesia (кроме служебных, таких как schema).
  2. Для каждой таблицы проверить, есть ли уже локальная дисковая копия (disc_copies).
  3. Если копии нет – добавить её вызовом mnesia:add_table_copy(Tab, node(), disc_copies).

Пример реализации

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,
            % Реплицируем все пользовательские таблицы на локальный узел
            Tables = mnesia:system_info(tables) -- [schema],
            lists:foreach(fun(Tab) ->
                case lists:member(node(), mnesia:table_info(Tab, disc_copies)) of
                    false ->
                        io:format("Adding local disc copy of table ~p...~n", [Tab]),
                        mnesia:add_table_copy(Tab, node(), disc_copies);
                    true ->
                        ok
                end
            end, Tables)
    end.

Интеграция

Этот код автоматически выполняется при старте приложения — он вызывается в infra_mnesia:init_tables/0 перед созданием таблиц и индексов.
Решение корректно работает как для нового узла, впервые присоединяющегося к кластеру, так и при перезапуске существующего узла: повторные вызовы mnesia:add_table_copy для уже существующих реплик игнорируются, не вызывая ошибок.

Проверка

  1. Запустите два узла, указав второму узел первого в extra_db_nodes (например, через переменную окружения или файл конфигурации).
  2. На каждом узле в консоли Erlang выполните:
    mnesia:table_info(user, disc_copies).
    

Ожидается, что список узлов, хранящих дисковую копию таблицы user, будет содержать оба узла.
Это означает, что после успешной репликации функция mnesia:table_info(user, disc_copies) на каждом узле должна возвращать список, в котором присутствуют имена обоих узлов, например:

[eventhub_a@localhost, eventhub_b@localhost]

Если список содержит только текущий узел — репликация не была выполнена, и данные не будут доступны на другом узле при его отключении.
Проверку необходимо выполнить для всех критически важных таблиц, перечисленных в задаче #14.

# План выполнения задачи #14 — Настройка репликации Mnesia ## Текущая ситуация - `cluster_discovery.erl` реализует динамическое обнаружение узлов через DNS-запрос (`eventhub-node`) и устанавливает Erlang-соединение между узлами (`net_kernel:connect_node`). - `infra_mnesia.erl` содержит `ensure_cluster_join/0`, которая добавляет узлы в кластер Mnesia через `extra_db_nodes` и копирует схему, но **не создаёт реплики таблиц** с данными. - Таким образом, после подключения нового узла данные не реплицируются — узел остаётся пустым. ## Цель Обеспечить автоматическую полную репликацию всех горячих таблиц (`user`, `event`, `booking`, `calendar` и др.) на каждый новый узел, присоединяющийся к кластеру, а также на существующие узлы при перезапуске. --- ### Шаг 1. Доработка `cluster_discovery.erl` **Файл:** `src/infra/cluster_discovery.erl` - После успешного `net_kernel:connect_node(Node)` добавить вызов функции, которая выполняет репликацию таблиц Mnesia. - Реализовать функцию `replicate_tables/0` (или `replicate_to_node/1`), которая: 1. Получает список всех таблиц Mnesia (кроме `schema`) через `mnesia:system_info(tables)`. 2. Для каждой таблицы, если локальный узел ещё не имеет дисковой копии (`disc_copies`), вызывает `mnesia:add_table_copy(Table, node(), disc_copies)`. - При старте в режимах `swarm` и `remote` (`eventhub_app.erl`) вызывать новую функцию `cluster_discovery:discover_and_replicate/0`, которая объединяет обнаружение и репликацию. **Примерный код:** ```erlang discover_and_replicate() -> % существующий код обнаружения... Nodes = discover_nodes(), lists:foreach(fun(Node) -> case net_kernel:connect_node(Node) of true -> replicate_to_node(Node); false -> io:format("Failed to connect to ~p~n", [Node]) end end, Nodes). replicate_to_node(Node) -> mnesia:change_config(extra_db_nodes, [Node]), Tables = mnesia:system_info(tables) -- [schema], lists:foreach(fun(Tab) -> case lists:member(node(), mnesia:table_info(Tab, disc_copies)) of false -> mnesia:add_table_copy(Tab, node(), disc_copies); true -> ok end end, Tables). ``` ## Шаг 2. Доработка `ensure_cluster_join/0` в `infra_mnesia.erl` ### Цель При запуске узла, которому в конфигурации заданы узлы кластера через `extra_db_nodes`, автоматически создавать локальные дисковые копии **всех** таблиц, чтобы новый узел становился полноценной репликой данных, а не только подключался к схеме. ### Текущее состояние Функция `ensure_cluster_join/0` в `src/infra/infra_mnesia.erl` вызывает `mnesia:change_config(extra_db_nodes, Nodes)` и, при необходимости, добавляет копию схемы (`mnesia:add_table_copy(schema, node(), disc_copies)`). Однако **реплики остальных таблиц не создаются**, поэтому данные на новом узле остаются недоступными до первого обращения (и то не полностью). ### Необходимые изменения 1. **После успешного подключения к кластеру** получить список всех таблиц Mnesia (кроме служебных, таких как `schema`). 2. Для каждой таблицы проверить, есть ли уже локальная дисковая копия (`disc_copies`). 3. Если копии нет – добавить её вызовом `mnesia:add_table_copy(Tab, node(), disc_copies)`. ### Пример реализации ```erlang 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, % Реплицируем все пользовательские таблицы на локальный узел Tables = mnesia:system_info(tables) -- [schema], lists:foreach(fun(Tab) -> case lists:member(node(), mnesia:table_info(Tab, disc_copies)) of false -> io:format("Adding local disc copy of table ~p...~n", [Tab]), mnesia:add_table_copy(Tab, node(), disc_copies); true -> ok end end, Tables) end. ``` ### Интеграция Этот код **автоматически выполняется при старте приложения** — он вызывается в `infra_mnesia:init_tables/0` перед созданием таблиц и индексов. Решение корректно работает как для нового узла, впервые присоединяющегося к кластеру, так и при перезапуске существующего узла: повторные вызовы `mnesia:add_table_copy` для уже существующих реплик игнорируются, не вызывая ошибок. ### Проверка 1. Запустите два узла, указав второму узел первого в `extra_db_nodes` (например, через переменную окружения или файл конфигурации). 2. На каждом узле в консоли Erlang выполните: ```erlang mnesia:table_info(user, disc_copies). ``` **Ожидается, что список узлов, хранящих дисковую копию таблицы `user`, будет содержать оба узла.** Это означает, что после успешной репликации функция `mnesia:table_info(user, disc_copies)` на каждом узле должна возвращать список, в котором присутствуют имена обоих узлов, например: ```erlang [eventhub_a@localhost, eventhub_b@localhost] ``` Если список содержит только текущий узел — репликация не была выполнена, и данные не будут доступны на другом узле при его отключении. Проверку необходимо выполнить для всех критически важных таблиц, перечисленных в задаче #14.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Reference: EventHub/EventHubBack#14