Обновление схемы данных без потерь #17

Closed
opened 2026-05-02 16:18:44 +03:00 by aleksey · 3 comments
Owner

Этап 5. Обновление схемы данных без потерь (пункт 6 задачи)

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

Шаги:

  1. Интеграция библиотеки migresia:
    • Добавить migresia как зависимость в rebar.config/erlang.mk.
    • Создать модуль migrations в проекте.
    • В infra_mnesia:start/0 после проверки существования таблиц вызывать migresia:migrate() для применения неприменённых миграций.
  2. Первая миграция:
    • Зафиксировать текущую расширенную схему (из этапа 0) как базовую миграцию (версия 1).
    • Последующие изменения полей/таблиц будут добавляться новыми миграциями.
  3. Процедура плавающего обновления узлов:
    • Разработать скрипт (или включить в релизную документацию) пошаговый процесс обновления кластера:
      1. Выбрать один узел, перевести в режим обслуживания (не принимать новые запросы, дождаться завершения текущих).
      2. Создать резервную копию всей Mnesia на этом узле (mnesia:backup("backup_node@host.bak")).
      3. Обновить код приложения (выкатить новую версию).
      4. Запустить приложение, дождаться применения миграций через migresia.
      5. Проверить согласованность данных (например, сравнить количество записей в ключевых таблицах с другими узлами).
      6. Повторить для остальных узлов по очереди.
    • Автоматизировать через Ansible или сценарии развертывания.
  4. Откат:
    • Предусмотреть команду отката миграций (если migresia поддерживает down-миграции) или восстановления из бекапа.
  5. Особенности архивных узлов:
    • Архивные таблицы создаются с той структурой, которая актуальна на момент переноса данных. Миграции к ним не применяются.
    • При чтении из архива, если структура изменилась, модуль archive_manager должен приводить поля к актуальному формату (либо хранить мапперы версий). В большинстве случаев, чтение просто игнорирует новые поля, поэтому совместимость сохраняется.
  6. Тестирование:
    • На staging-кластере провести полный цикл обновления: бекап, миграция, проверка данных, откат.

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

## Этап 5. Обновление схемы данных без потерь (пункт 6 задачи) **Цель**: внедрить механизм версионирования схемы и процедуру плавающего обновления, позволяющую добавлять/изменять таблицы и поля без остановки всего сервиса и без потери данных. **Шаги**: 1. **Интеграция библиотеки `migresia`**: - Добавить `migresia` как зависимость в rebar.config/erlang.mk. - Создать модуль `migrations` в проекте. - В `infra_mnesia:start/0` после проверки существования таблиц вызывать `migresia:migrate()` для применения неприменённых миграций. 2. **Первая миграция**: - Зафиксировать текущую расширенную схему (из этапа 0) как базовую миграцию (версия 1). - Последующие изменения полей/таблиц будут добавляться новыми миграциями. 3. **Процедура плавающего обновления узлов**: - Разработать скрипт (или включить в релизную документацию) пошаговый процесс обновления кластера: 1. Выбрать один узел, перевести в режим обслуживания (не принимать новые запросы, дождаться завершения текущих). 2. Создать резервную копию всей Mnesia на этом узле (`mnesia:backup("backup_node@host.bak")`). 3. Обновить код приложения (выкатить новую версию). 4. Запустить приложение, дождаться применения миграций через `migresia`. 5. Проверить согласованность данных (например, сравнить количество записей в ключевых таблицах с другими узлами). 6. Повторить для остальных узлов по очереди. - Автоматизировать через Ansible или сценарии развертывания. 4. **Откат**: - Предусмотреть команду отката миграций (если `migresia` поддерживает down-миграции) или восстановления из бекапа. 5. **Особенности архивных узлов**: - Архивные таблицы создаются с той структурой, которая актуальна на момент переноса данных. Миграции к ним не применяются. - При чтении из архива, если структура изменилась, модуль `archive_manager` должен приводить поля к актуальному формату (либо хранить мапперы версий). В большинстве случаев, чтение просто игнорирует новые поля, поэтому совместимость сохраняется. 6. **Тестирование**: - На staging-кластере провести полный цикл обновления: бекап, миграция, проверка данных, откат. **Результат**: надёжный процесс изменения схемы данных без потерь и незаметного для пользователей простоя.
aleksey added the Future label 2026-05-02 16:18:44 +03:00
Author
Owner

План выполнения Задачи #17 – Обновление схемы данных без потерь

1. Анализ и цели

После задач #12–#16 база стабильна, но потребуются изменения схемы в будущем.
Цели:

  • Версионирование схемы через миграции.
  • Последовательное обновление узлов (rolling update) без потери данных.
  • Возможность отката (backup + down-миграции).
  • Минимальное влияние на производительность.

2. Выбор инструмента

Реализуем собственный легковесный механизм миграций:

  • Таблица Mnesia schema_migrations хранит историю применённых миграций.
  • Миграции загружаются из priv/migrations/ (или настраиваемого пути).
  • Каждая миграция – модуль с функциями up/0 и down/0.
  • Интеграция в infra_mnesia:init_tables/0.

3. Детальный план реализации

3.1. Создание модуля migration_engine

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

API:

  • init_migrations_table() – создаёт schema_migrations, если нет.
  • apply_pending() – компилирует и применяет новые миграции.
  • rollback(Version) – откат к указанной версии.
  • status() – список применённых и ожидающих миграций.

3.2. Интеграция в infra_mnesia.erl

После create_indices() добавить:

ok = migration_engine:init_migrations_table(),
ok = migration_engine:apply_pending(),

3.3. Первая миграция (фиксация текущей схемы)

Создать priv/migrations/20260401120000_base_schema.erl с пустыми up/0 и down/0.

3.4. Процедура плавающего обновления

Скрипт: scripts/rolling_update.sh

  1. Узел в обслуживание.
  2. Бэкап mnesia:backup("backup.bak").
  3. Обновить код.
  4. Запустить, применить миграции.
  5. Проверить согласованность.
  6. Вернуть узел.
  7. Повторить для остальных.

3.5. Откат

  • Если down/0 реализована: migration_engine:rollback(Version).
  • Или восстановить из бэкапа: mnesia:restore(...).

3.6. Архивные узлы

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

4. Тестирование

  • Локально (один узел): создать тестовую миграцию, применить, откатить.
  • Staging (кластер): выполнить rolling update, проверить репликацию и отказоустойчивость.
# План выполнения Задачи #17 – Обновление схемы данных без потерь ## 1. Анализ и цели После задач #12–#16 база стабильна, но потребуются изменения схемы в будущем. **Цели:** - Версионирование схемы через миграции. - Последовательное обновление узлов (rolling update) без потери данных. - Возможность отката (backup + down-миграции). - Минимальное влияние на производительность. ## 2. Выбор инструмента Реализуем собственный легковесный механизм миграций: - Таблица Mnesia `schema_migrations` хранит историю применённых миграций. - Миграции загружаются из `priv/migrations/` (или настраиваемого пути). - Каждая миграция – модуль с функциями `up/0` и `down/0`. - Интеграция в `infra_mnesia:init_tables/0`. ## 3. Детальный план реализации ### 3.1. Создание модуля `migration_engine` **Файл:** `src/infra/migration_engine.erl` **API:** - `init_migrations_table()` – создаёт `schema_migrations`, если нет. - `apply_pending()` – компилирует и применяет новые миграции. - `rollback(Version)` – откат к указанной версии. - `status()` – список применённых и ожидающих миграций. ### 3.2. Интеграция в `infra_mnesia.erl` После `create_indices()` добавить: ok = migration_engine:init_migrations_table(), ok = migration_engine:apply_pending(), ### 3.3. Первая миграция (фиксация текущей схемы) Создать `priv/migrations/20260401120000_base_schema.erl` с пустыми `up/0` и `down/0`. ### 3.4. Процедура плавающего обновления **Скрипт:** `scripts/rolling_update.sh` 1. Узел в обслуживание. 2. Бэкап `mnesia:backup("backup.bak")`. 3. Обновить код. 4. Запустить, применить миграции. 5. Проверить согласованность. 6. Вернуть узел. 7. Повторить для остальных. ### 3.5. Откат - Если `down/0` реализована: `migration_engine:rollback(Version)`. - Или восстановить из бэкапа: `mnesia:restore(...)`. ### 3.6. Архивные узлы - Миграции не применяются к архивным таблицам (созданы со структурой на момент архивации). - При чтении игнорировать неизвестные поля. ## 4. Тестирование - **Локально (один узел):** создать тестовую миграцию, применить, откатить. - **Staging (кластер):** выполнить rolling update, проверить репликацию и отказоустойчивость.
Author
Owner

Тестирование миграций (задача #17)

1. Подготовка окружения

Остановите приложение, если запущено, и очистите данные:

rm -rf Mnesia.*
mkdir -p priv/migrations

2. Базовая миграция (уже должна быть создана)

Файл priv/migrations/20260501120000_base_schema.erl с пустыми up/0 и down/0.

3. Тестовая миграция

Создайте файл priv/migrations/20260504150000_test_migration.erl со следующим содержимым:

-module('20260504150000_test_migration').
-behaviour(db_migration).
-export([up/0, down/0]).

up() ->
    io:format("Test migration UP applied!~n"),
    ok.

down() ->
    io:format("Test migration DOWN applied!~n"),
    ok.

4. Запуск приложения и проверка

make shell

После старта вы увидите в консоли сообщение "Test migration UP applied!".

Теперь в Erlang-консоли проверьте статус миграций:

migration_engine:status().

Ожидается:

#{applied => ["20260501120000_base_schema", "20260504150000_test_migration"],
  pending => []}

5. Откат тестовой миграции

Выполните:

migration_engine:rollback("20260501120000_base_schema").

В консоли появится "Test migration DOWN applied!".

Проверьте статус:

migration_engine:status().

Теперь applied должна содержать только базовую миграцию.

6. Завершение

После отката можно снова запустить migration_engine:apply_pending(), чтобы миграция опять применилась (для повторных тестов).

Удалите тестовую миграцию, когда закончите, чтобы она не мешала в будущем.

# Тестирование миграций (задача #17) ## 1. Подготовка окружения Остановите приложение, если запущено, и очистите данные: rm -rf Mnesia.* mkdir -p priv/migrations ## 2. Базовая миграция (уже должна быть создана) Файл `priv/migrations/20260501120000_base_schema.erl` с пустыми `up/0` и `down/0`. ## 3. Тестовая миграция Создайте файл `priv/migrations/20260504150000_test_migration.erl` со следующим содержимым: -module('20260504150000_test_migration'). -behaviour(db_migration). -export([up/0, down/0]). up() -> io:format("Test migration UP applied!~n"), ok. down() -> io:format("Test migration DOWN applied!~n"), ok. ## 4. Запуск приложения и проверка make shell После старта вы увидите в консоли сообщение "Test migration UP applied!". Теперь в Erlang-консоли проверьте статус миграций: migration_engine:status(). Ожидается: #{applied => ["20260501120000_base_schema", "20260504150000_test_migration"], pending => []} ## 5. Откат тестовой миграции Выполните: migration_engine:rollback("20260501120000_base_schema"). В консоли появится "Test migration DOWN applied!". Проверьте статус: migration_engine:status(). Теперь `applied` должна содержать только базовую миграцию. ## 6. Завершение После отката можно снова запустить `migration_engine:apply_pending()`, чтобы миграция опять применилась (для повторных тестов). Удалите тестовую миграцию, когда закончите, чтобы она не мешала в будущем.
Author
Owner

Инструкция по проверке миграций:

  1. Запустите приложение с чистой базой:
    rm -rf Mnesia.* && make shell

  2. В Erlang-консоли:
    rr("include/records.hrl").

  3. Проверьте статус до применения:
    migration_engine:status().
    Ожидается: #{pending => [...], applied => []}

  4. Примените неприменённые миграции:
    migration_engine:apply_pending().
    В консоли должно появиться "Test migration UP applied!".

  5. Проверьте статус после применения:
    migration_engine:status().
    Ожидается: #{pending => [], applied => ["20260504150000_test_migration", "20260501120000_base_schema"]}

  6. Проверьте таблицу schema_migrations:
    mnesia:dirty_match_object(#schema_migration{_ = '_'}).
    Вернет две записи с полями version и applied_at.

  7. Откатите тестовую миграцию:
    migration_engine:rollback("20260501120000_base_schema").
    В консоли появится "Test migration DOWN applied!".

  8. Проверьте статус после отката:
    migration_engine:status().
    applied должно содержать только базовую миграцию.

  9. Повторно примените миграции:
    migration_engine:apply_pending().
    Снова появится "Test migration UP applied!".
    Статус вернется к applied = обе миграции.

Таким образом, механизм миграций работает корректно.

Инструкция по проверке миграций: 1. Запустите приложение с чистой базой: rm -rf Mnesia.* && make shell 2. В Erlang-консоли: rr("include/records.hrl"). 3. Проверьте статус до применения: migration_engine:status(). Ожидается: #{pending => [...], applied => []} 4. Примените неприменённые миграции: migration_engine:apply_pending(). В консоли должно появиться "Test migration UP applied!". 5. Проверьте статус после применения: migration_engine:status(). Ожидается: #{pending => [], applied => ["20260504150000_test_migration", "20260501120000_base_schema"]} 6. Проверьте таблицу schema_migrations: mnesia:dirty_match_object(#schema_migration{_ = '_'}). Вернет две записи с полями version и applied_at. 7. Откатите тестовую миграцию: migration_engine:rollback("20260501120000_base_schema"). В консоли появится "Test migration DOWN applied!". 8. Проверьте статус после отката: migration_engine:status(). applied должно содержать только базовую миграцию. 9. Повторно примените миграции: migration_engine:apply_pending(). Снова появится "Test migration UP applied!". Статус вернется к applied = обе миграции. Таким образом, механизм миграций работает корректно.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Reference: EventHub/EventHubBack#17