Mnesia migrations with global lock on startup. Refs EventHub/EventHubBack#24

This commit is contained in:
2026-07-07 17:54:29 +03:00
parent 6e1157134c
commit 7c5f2d26b8
5 changed files with 806 additions and 647 deletions
+43 -23
View File
@@ -1,23 +1,43 @@
# Миграции схемы данных EventHub
## Применение миграций
При старте приложения автоматически выполняются все неприменённые миграции.
Для ручного запуска можно вызвать:
migration_engine:apply_pending().
## Создание новой миграции
1. Создайте файл в `priv/migrations/` с именем вида `YYYYMMDDHHMMSS_описание.erl`.
2. Реализуйте поведение `db_migration` (функции `up/0` и `down/0`).
3. При очередном запуске приложения миграция будет применена.
## Откат миграций
migration_engine:rollback("20260501120000_base_schema").
## Плавающее обновление (rolling update)
1. Переведите узел в режим обслуживания.
2. Выполните бэкап: `mnesia:backup("backup_node.bak")`.
3. Обновите код приложения (git pull / rsync).
4. Перезапустите узел – миграции применятся автоматически.
5. Убедитесь в согласованности данных.
6. Верните узел в работу.
7. Повторите для остальных узлов.
# Миграции схемы данных EventHub
## Когда применяются
После `infra_mnesia:init_tables()` и `wait_for_tables()` приложение вызывает
`migration_engine:ensure_applied/0`:
1. Узел, захвативший **глобальную блокировку** в Mnesia (`schema_migration`, ключ `__migration_lock__`), применяет все pending-миграции.
2. Остальные узлы (join кластера) **ждут**, пока pending-список станет пустым, и не стартуют HTTP до синхронизации.
Базовые таблицы создаёт `infra_mnesia` при старте. Миграции — только для **инкрементальных** изменений (индексы, трансформация данных, новые поля).
## Создание новой миграции
1. Создайте файл в `src/migrations/` с именем `YYYYMMDDHHMMSS_описание.erl`.
2. Реализуйте `up/0` и `down/0`.
3. Добавьте модуль в список `?ALL_MIGRATIONS` в `src/infra/migration_engine.erl` (в конец, по возрастанию версии).
4. При следующем старте миграция применится автоматически (на узле с lock).
## Ручной запуск
```erlang
migration_engine:apply_pending().
migration_engine:status().
migration_engine:rollback("20260501120000_base_schema").
```
## Плавающее обновление (rolling update)
1. Переведите узел в режим обслуживания.
2. Выполните бэкап: `mnesia:backup("backup_node.bak")`.
3. Обновите код приложения (git pull / rsync).
4. Перезапустите узел — `ensure_applied/0` применит только новые миграции (один узел с lock).
5. Убедитесь: `migration_engine:status()``pending => []` на всех узлах.
6. Верните узел в работу.
7. Повторите для остальных узлов.
## Восстановление после сбоя
Если узел упал во время миграции, lock в `schema_migration` может остаться.
Он считается устаревшим через **5 минут** (`?LOCK_STALE_SECONDS`) — следующий стартующий узел перехватит lock и продолжит.
При ошибке в `up/0` приложение не стартует (`{error, {migration_failed, ...}}`). Откатите код или исправьте миграцию, при необходимости восстановите из `mnesia:backup/1`.