Skip to content

0059. Обновление со сменой схемы — после проверенной резервной копии ​

Статус: accepted. Owner: Ian Panaev. Дата: 2026-10-03. Изменяет запрет смены схемы в ADR 0038: раньше единственным путём был ручной upgrade --backup-record. Опирается на агент резервных копий из ADR 0056 и ADR 0057.

Контекст ​

Встроенный механизм обновлений скачивает stable release с GitHub, проверяет архив, останавливает службы, переключает версию и при отказе запуска возвращает прежний код. Так он работает только при той же схеме БД. Релиз со сменой схемы отклонялся везде: в консоли и автообновлении (maintenance_required), в update и в пакетах deb/rpm/Windows, которые над существующей установкой вызывают apply-installer. Новый пакет поверх установки со старой схемой падал в postinst. Оставался ручной upgrade --backup-record <файл>, а этот файл — запись оператора, а не проверка копии.

Почему схему нельзя просто мигрировать вместе с кодом:

  • после успешной миграции прежний код не работает: ready() отказывает на схеме новее своей. Вернуться можно только к резервной копии;
  • миграции идут одной транзакцией под блокировкой 18471/1. Упавшая миграция откатывается целиком, схема остаётся прежней. Затем вне транзакции строятся онлайн-индексы (CREATE INDEX CONCURRENTLY), шаг повторяемый;
  • агент резервных копий снимает точку онлайн, пока службы работают. После снимка он ставит её структурную проверку.

Решение ​

Смена схемы ставится всеми путями обновления — консоль, автообновление в окне, update, пакеты через apply-installer. Обязательное условие: перед остановкой служб встроенный агент снял свежую точку в настроенный vault и структурно её проверил.

  1. Релиз скачивается, проверяется и распаковывается, как раньше.
  2. Сторож (apps/deploy/src/backup-guard.ts) работает с API текущей версии по ключу bootstrap и обычному локальному транспорту, включая закреплённый сертификат. Он проверяет четыре условия: vault подключён, vault доступен, агент в сети, есть хотя бы одна завершённая копия. Первая полная копия терабайтов — плановая операция, а не побочный эффект обновления.
  3. POST /api/v1/backup/runs с новым Idempotency-Key. Сторож ждёт completed с pointId, затем verifiedAt без verifyError у этой точки. Опрос идёт раз в 5 с. Временные ошибки API повторяются, общий предел — 6 ч. Пока идёт ожидание, планировщик консоли продолжает писать heartbeat.
  4. Любой отказ сторожа (BackupRequired) возникает до любых изменений: службы работают, журнал не создан. Консоль показывает maintenance_required с объяснением. Автообновление повторит попытку не раньше следующих суток. Пакет завершает postinst ошибкой с той же подсказкой.
  5. Сценарий applyMigration (apps/deploy/src/migration-update.ts) пишет журнал с точкой копии. Фазы: prepared, stopped, migrating, migrated, committed.
    • Упавшая миграция откатывает свою транзакцию. Прежний релиз запускается снова (rolling-back → rolled-back), сервис продолжает работать на старой версии.
    • Если прежний релиз отказывает, значит упал шаг индексов уже после фиксации схемы. Тогда журнал переходит в maintenance-required.
    • Отказ запуска нового релиза после успешной миграции тоже даёт maintenance-required. Прежний код на такой схеме не запускается; точка копии — граница восстановления.
  6. recover для журнала со сменой схемы действует по фазе:
    • из prepared/stopped возвращает прежний релиз: база не тронута;
    • из любой более поздней фазы идёт вперёд: повторяет миграцию (она идемпотентна) и запускает новый релиз.
  7. Ручной upgrade --backup-record остаётся для установок без встроенного vault. Он использует тот же журнал и тот же откат.

Что теряется при восстановлении из точки. Изменения, принятые после снимка и до остановки служб, в точку не попадают. Точка нужна только при отказе нового релиза после миграции. Тогда оператор выбирает: исправить причину и выполнить recover (вперёд) или восстановить точку прежним релизом (BACKUP_RECOVERY).

Переход с 0.2.0. В 0.2.0 нет API резервных копий, а её планировщик сам отклоняет смену схемы. Поэтому на релиз с этим решением установку 0.2.0 переводят один раз вручную: upgrade --backup-record после копии оператора, либо пакет с последующим upgrade. Дальнейшие обновления идут автоматически.

Последствия ​

  • Обновление со сменой схемы длится дольше на время снимка изменений и его проверки. При ежедневных копиях это минуты, а не часы.
  • Автообновление со сменой схемы включено так же, как обычное: окно, одна попытка в сутки, pin запрещает. Кто не хочет миграций без человека, закрепляет версию.
  • Публичный контракт /api/v1 не меняется: используются существующие backup/* и код maintenance_required, тексты консоли уточнены.
  • Не реализовано: восстановление в консоли, pin точки перед обновлением (точка живёт по retention, как любая другая), rolling update и HA.

Проверки ​

  • tests/schema-update.test.mjs:
    • отказ сторожа без vault, без доступа к vault, без агента, без первой копии и на релизе без API копий;
    • неудачная копия, повреждённая точка, тайм-аут, неподтверждённое завершение;
    • порядок фаз, откат при неудачной миграции, отказ без копии без единой остановки, сбой запуска после миграции.
  • tests/update-monitor.test.mjs: смена схемы ставится в окне; отказ копии даёт maintenance_required и одну попытку в сутки.
  • tests/deployment.test.mjs: решение migrate для новой схемы, отказ для схемы ниже текущей.
  • tests/browser/updates.mjs: установка со сменой схемы доступна и объяснена; отказ показан.
  • tests/deployment/containers.mjs (Compose, настоящие API, агент и vault). Релиз с номером схемы выше текущего:
    • до подключения vault отклоняется, версия и службы не меняются;
    • после первой копии ставится, а журнал называет новую точку, а не прежнюю.
  • tests/schema-update.test.mjs: направление recover — назад до migrating, вперёд после него.

Arkvory · ProAnimaStudio