Миграции базы и совместимость отката
В прошлом уроке данные получили независимое от контейнера место. Теперь усложним выпуск: приложению требуется поле краткого описания статьи. Нельзя просто изменить базу и считать, что возврат прежнего образа отменит это действие. Схема и записи живут отдельно от кода, поэтому план публикации должен описывать их совместимость.
У учебной PostgreSQL 16 существует таблица articles(id, title) с двумя условными строками. Назовём старый прикладной код app-r1, а новый — app-r2. WSGI-индикатор прежнего урока остаётся индикатором; здесь рассматриваем будущие операции приложения над таблицей. SQL представлен для выделенной учебной базы, не выполнялся и не описывает настоящую базу ProfessorWeb.
Почему прямое переименование ломает возврат
Предположим, что автор хочет заменить title на headline и сразу удаляет прежнее имя. Старый код продолжает обращаться к title, поэтому во время перехода запросы завершаются ошибкой. Возврат старого контейнера проблему не устраняет: база уже имеет другое устройство. Восстановление дампа способно вернуть схему, но вместе с ней потерять записи, созданные после момента копии. Для обычного обновления это слишком сильное действие.
Более удобная модель разделяет расширение и удаление. Сначала добавляют совместимое поле, затем публикуют код, который умеет работать с переходной схемой. Данные заполняют отдельно и проверяют полноту. Удаление старого поля, если оно вообще нужно, откладывают до завершения периода совместимости. Это не запрет на изменение схемы, а способ сделать промежуточные состояния пригодными для обслуживания.
Добавить поле без немедленного требования
Для нашего примера title остаётся, а summary появляется как необязательное текстовое поле. Прежний код, использующий явный список колонок, продолжает читать и создавать статьи без описания. Новый код понимает NULL: если описание пока отсутствует, не отображает пустой блок или выбирает корректный запасной текст. Совместимость зависит не только от SQL, но и от этих прикладных правил.
BEGIN;
SET LOCAL lock_timeout = '3s';
ALTER TABLE articles ADD COLUMN summary text;
COMMIT;
Операция требует блокировок, даже если изменение кажется маленьким. Учебный lock_timeout ограничивает ожидание, а не гарантирует мгновенное завершение любой миграции. При превышении ожидания миграция отклоняется, после чего выясняют, какие операции удерживали нужный ресурс. Не следует автоматически снимать ограничение и ждать бесконечно в окне выпуска. Описание ALTER TABLE PostgreSQL 16.
Здесь миграция показана один раз для известной начальной схемы. Повторное выполнение должно управляться журналом применённых миграций выбранного инструмента или проверкой ожидаемого состояния. Простое добавление IF NOT EXISTS не подтверждает, что уже существующее поле имеет правильный тип и смысл. Оно может скрыть несовпадение. В выпуске фиксируют идентификатор изменения схемы и его фактический результат.
Переходные правила приложения
Код app-r2 записывает описание, если оно предоставлено, но допускает отсутствие у старых строк. Код app-r1 не обязан знать новое поле, однако не должен уничтожать его при обновлении заголовка. Например, явное UPDATE articles SET title = ... WHERE id = ... сохраняет описание. Приём «удалить строку и заново вставить только известные поля» может его потерять. Поэтому совместимость проверяют на уровне конкретных операций чтения и записи, а не по одному успешному подключению.
Учебная матрица после расширения схемы
app-r1: читает title; создаёт строку без summary; сохраняет чужие поля
app-r2: читает title и nullable summary; записывает summary при наличии
Возврат app-r2 → app-r1: допустим, если эти условия подтверждены
Удаление summary: не является частью быстрого возврата кода
Матрица не утверждает, что настоящее приложение уже реализовано и протестировано. Она задаёт требования будущему выпуску. Для проверки используют копию базы, где есть и старые строки без описания, и новые строки с ним. Именно смешанное состояние показывает полезность переходного режима. Пустая база способна скрыть проблему, потому что все созданные новым кодом записи уже имеют новое поле.
Заполнение данных отдельным шагом
Добавление колонки не даёт содержательного описания каждой статье. Его нельзя механически копировать из заголовка и считать задачу завершённой. В учебной таблице заполним одну строку, чтобы показать смешанное состояние. На большой базе обработку организуют небольшими понятными порциями, с возможностью возобновления и контролем результата. Продолжительная массовая операция имеет другой профиль блокировок и нагрузки, чем изменение структуры.
UPDATE articles
SET summary = 'Разбираем подготовку публичного результата из Markdown.'
WHERE id = 1 AND summary IS NULL;
SELECT id, title, summary FROM articles ORDER BY id;
Условие summary IS NULL помогает не перезаписать уже отредактированное описание при повторной обработке. Это небольшая прикладная защита, а не общий механизм конкуренции любых писателей. После будущего выполнения ожидается одна заполненная строка и одна без описания. Такой результат допустим во время перехода и должен правильно отображаться обоими выбранными вариантами кода.
Когда можно ужесточить схему
Если бизнес-правило требует обязательного описания, ограничение вводят после заполнения и проверки всех источников записи. Прежний код, который создаёт строку без summary, тогда больше несовместим. Даже если таблица сейчас не содержит NULL, новый запрос старого процесса может их создать до применения ограничения. Поэтому сначала прекращают старый способ записи, подтверждают новый, затем меняют правило базы.
Момент ужесточения меняет условия отката. После него нельзя обещать возврат к любому прошлому образу. В журнале указывают минимально совместимый прикладной выпуск и отдельный план на случай ошибки схемы. Иногда безопаснее исправить новый код, чем откатывать его к версии с несовместимыми операциями. Для разрушительных изменений требуется заранее проверенная копия и решение о допустимой потере данных, а не автоматическая команда down у каждой миграции.
Выполнение в порядке выпуска
Миграцию выполняет отдельная уполномоченная роль, а не каждый worker при первом запросе. Несколько процессов могут одновременно попытаться применить одну операцию. Порядок должен быть видимым: подготовлена копия, применено расширение, подтверждена схема, опубликован совместимый код, затем выполнено заполнение. Только после отдельного принятия перехода допустимо удаление старых возможностей.
Наш результат — схема с дополнительным nullable-полем и правила для двух версий кода. Возврат образа теперь описан через совместимость, а не через надежду, что база «сама станет прежней». Следующий урок уточнит роли и секреты, чтобы право менять схему не оказалось у любого процесса, который лишь читает статьи.