Учебник веб-разработки
Разделы учебника
На этой странице

XI. DevOps

Миграции схемы и совместимость релизов

Оглавление · Жизненный цикл PostgreSQL · Доступ к данным и перенос

Задача: планировать изменение схемы вместе с читателями, писателями и данными. SQL ниже — самостоятельная учебная модель PostgreSQL 17, не таблицы Strapi.

Изменение — функция с предусловиями

Миграция переводит схему и данные (S₀, D₀) в (S₁, D₁). У неё есть предусловия, постусловия и допустимые сбои. Например, добавление NOT NULL требует отсутствия NULL в старых строках и способности всех будущих писателей задавать значение. Наличие файла миграции в Git не подтверждает выполнение на конкретной БД.

Исходник схемы
flowchart LR
  Before["Схема S0 и данные D0"] --> Preconditions["Проверить предусловия"]
  Preconditions --> Change["Изменить схему и данные"]
  Change --> Verify["Проверить ограничения и сценарии"]
  Verify --> After["Схема S1 и данные D1"]
  Change --> Failure["Rollback транзакции или отдельный план восстановления"]
ОперацияЧто меняетПочему повторный запуск требует правил
Init data directoryСоздаёт новый серверный набор данныхНе предназначен для существующего volume
Schema migrationТаблицы, поля, ограничения, индексыНужно знать исходную версию и уже выполненные шаги
BackfillЗначения существующих строкЧастичный результат, гонки с редактором
Импорт контентаДокументы CMS и медиаКонфликты, drafts, редакторские правки
RestoreВозвращает сохранённое состояниеМожет потерять записи после даты копии

Журнал миграций отмечает выполненные шаги; он не доказывает, что схема не была изменена вручную. IF NOT EXISTS убирает одну ошибку повтора, но не проверяет, что существующий объект имеет нужный тип и ограничения. Постусловия проверяют явно.

Совместимость важнее порядка номеров

Обозначим Compatible(C,S,D) — версия кода C корректно читает и пишет данные D при схеме S. Для переключения нужно проверить новую версию; для возврата — прежнюю. Читатель может понимать новую колонку, но старый INSERT уже нарушать её NOT NULL. Поэтому отдельно проверяют чтение и запись, API и семантику значений.

В Atmanki web и CMS — разные релизы. Совместимость web с CMS API не заменяет совместимость Strapi со своей физической схемой. Сохранённый digest старого образа помогает получить код, но не возвращает старые данные.

Expand, backfill, contract

Expand добавляет совместимую возможность, например nullable-колонку. Backfill заполняет старые данные по явному правилу. Затем код начинает использовать новое представление. Contract удаляет старое или ужесточает ограничения только после исчезновения несовместимых читателей и писателей.

Исходник схемы
flowchart TD
  Expand["Добавить новую возможность без требования к старым писателям"] --> Backfill["Заполнить данные; сверить результат"]
  Backfill --> Code["Переключить всех нужных читателей и писателей"]
  Code --> Gate{"Совместимость и новые записи проверены?"}
  Gate -->|да| Contract["Удалить старое / ужесточить ограничение"]
  Gate -->|нет| Hold["Сохранить совместимую схему; исправить причину"]

Это стратегия, а не обещание нулевого downtime. На одном VPS согласованное окно обслуживания иногда проще нескольких переходных релизов. Rename особенно требует внимания: добавление нового поля само по себе не переносит значения и не синхронизирует две версии. Если старый писатель обновляет только старое поле, новое может устареть. Нужны правила записи, порядок отключения старых писателей и проверка расхождений.

Для backfill выбирают устойчивый ключ и конечные пачки, фиксируют прогресс, проверяют повторы. Заполняя только NULL, не переписывают уже заполненное значение, но это не гарантирует его правильность. Значение default должно иметь смысл для новой записи: произвольная заглушка ради зелёной миграции скрывает дефект данных.

Маленький SQL-опыт без таблиц CMS

Выполняйте в отдельном учебном соединении. Временная таблица и вся транзакция откатываются; это демонстрация изменения формы, не сценарий production-релиза.

BEGIN;
SET LOCAL lock_timeout = '2s';
SET LOCAL statement_timeout = '10s';

CREATE TEMP TABLE lesson_migration (
  id integer PRIMARY KEY,
  title text NOT NULL
);
INSERT INTO lesson_migration VALUES (1, 'Первая'), (2, 'Вторая');

ALTER TABLE lesson_migration ADD COLUMN display_label text;
SELECT count(*) AS missing_before
FROM lesson_migration WHERE display_label IS NULL;

UPDATE lesson_migration SET display_label = title WHERE display_label IS NULL;
SELECT id, title, display_label FROM lesson_migration ORDER BY id;
SELECT count(*) AS missing_after
FROM lesson_migration WHERE display_label IS NULL;

ALTER TABLE lesson_migration ALTER COLUMN display_label SET NOT NULL;
ROLLBACK;

Ожидаются missing_before=2, missing_after=0 и две строки с совпадающими значениями. SQL в этом этапе не исполнялся. В реальном приложении после SET NOT NULL старый INSERT только с id/title перестанет работать: успешный backfill не обновляет код. Повторите такой INSERT отдельным опытом до ROLLBACK и наблюдайте отказ; после ошибки нужен rollback либо заранее созданный savepoint. Это показывает границу совместимости.

SET LOCAL действует в транзакции. lock_timeout ограничивает ожидание блокировки, statement_timeout — время команды; timeout не делает действие быстрым и не проверяет бизнес-смысл. Timeouts PostgreSQL.

Большинство ALTER TABLE берут сильные блокировки; точный режим зависит от формы команды. SET NOT NULL может требовать проверки существующих строк. Изменение на двух строках не доказывает приемлемую паузу на большой таблице. ALTER TABLE PostgreSQL. Блокировки обычно держатся до конца транзакции; длинный backfill вместе с DDL может надолго заблокировать другие операции. Блокировки.

CREATE INDEX CONCURRENTLY снижает блокирование записи по сравнению с обычным созданием индекса, но не выполняется внутри transaction block. Неудачный запуск может оставить invalid index. Поэтому «завернуть всё в BEGIN» не универсальный runner миграций. CREATE INDEX.

Strapi управляет собственной схемой

В нашем проекте content-type schemas находятся в infra/strapi/src/api и components; конфигурация database.ts задаёт подключение, а не ручную SQL-миграцию этих таблиц. Strapi синхронизирует схему с моделями при запуске. Переименование или удаление поля требует плана данных: запуск старого CMS image может быть новым изменением схемы, а не безвредным возвратом кода. Конфигурация БД Strapi.

Пользовательские database migrations — отдельный механизм Strapi. Документация Strapi 5 помечает его experimental: файлы выполняются при запуске до schema sync, каждый в своей транзакции; встроенного down нет. Поэтому up работает с прежней схемой, а не автоматически с новыми content-type полями. Database migrations Strapi. Этот порядок не переносим из TypeORM; перед реальным изменением проверяем поведение установленной версии 5.56.0 на изолированной копии. В checkout нет собственных файлов database/migrations; наш importer использует CMS API и явный запуск. Он не является системой миграций физической схемы. TypeORM в Strapi не устанавливаем.

Не применяйте учебный ALTER к внутренним таблицам CMS. Если переносите данные из PHP/MySQL-приложения, сначала определите модели CMS и способ импорта; подключение Strapi к произвольной старой базе не превращает её в готовую CMS. Маршрут переноса.

Что делать при частичном результате

Ошибка до COMMIT в транзакционном шаге и сбой после подтверждённого COMMIT — разные состояния. В первом случае откатывают транзакцию. Во втором проверяют, какие постусловия уже выполнены, и продолжают по журналу или новой миграции. При потере соединения результат COMMIT может быть неизвестен; нельзя считать, что шаг обязательно не выполнился. Внешние HTTP/S3-эффекты не отменяются SQL-rollback.

Down migration тоже преобразует данные. Удалённый текст нельзя восстановить одним ADD COLUMN, а сужение типа может потерять новые значения. Возврат старого образа не равно обратной миграции; восстановление копии не равно сохранению всех новых записей. Выбирают между совместимым возвратом кода, исправлением вперёд и восстановлением с явно оценённой потерей данных.

План проверяемого изменения

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

Для Atmanki используйте процедуры CMS, деплой и восстановление. Никакая миграция, смена версии сервера или восстановление production в рамках этой главы не выполняется.