Изменение схемы на пустой базе занимает мгновение. На большой таблице та же команда может ждать чужую транзакцию, переписывать данные или удерживать блокировку дольше, чем приложение готово терпеть. Короткий SQL-файл ещё не означает короткое воздействие на сервис.
У разных форм ALTER TABLE различаются блокировки и объём работы. PostgreSQL позволяет добавлять некоторые ограничения как NOT VALID, а проверять существующие строки отдельной командой VALIDATE CONSTRAINT. Новые изменения при этом уже должны удовлетворять добавленному ограничению. PostgreSQL 18: ALTER TABLE
Разделить изменение на этапы
Пусть в учебном каталоге вместо одного поля display_name нужны given_name и family_name. Переименование столбца не решает задачу: старый экземпляр приложения продолжит искать прежнее имя, а автоматическое разделение строки не гарантирует корректных данных.
Сначала схема расширяется новыми необязательными полями. Следующая версия приложения умеет читать обе формы и сохраняет согласованное представление по выбранному правилу. После этого данные заполняются порциями с явным учётом неоднозначных строк. Только когда старые версии перестали использовать прежний контракт, можно обсуждать его удаление.
Это последовательность совместимости, а не обещание миграции без риска. Нужно определить, что происходит с записью, которую пользователь изменил во время заполнения. Один вариант — условное обновление лишь строк, где новые поля ещё пусты; другой — версия записи и проверка, что исходные данные не менялись. Выбор зависит от смысла данных.
Ограничение отдельно от большого сканирования
Пример с учебным полем amount:
BEGIN;
SET LOCAL lock_timeout = '1s';
ALTER TABLE demo_orders
ADD CONSTRAINT demo_orders_amount_check
CHECK (amount >= 0) NOT VALID;
COMMIT;
-- Отдельный этап, после разбора старых нарушений:
ALTER TABLE demo_orders
VALIDATE CONSTRAINT demo_orders_amount_check;
CHECK в этом виде допускает NULL: выражение с неизвестным результатом не считается нарушением. Если бизнес-контракт запрещает NULL, это нужно задать отдельно. Учебная таблица предполагается существующей; команды не являются готовой миграцией произвольной рабочей базы.
Значение lock_timeout здесь иллюстрирует предпочтение быстрого контролируемого отказа перед неожиданным долгим ожиданием. Оно не универсально. План миграции должен описывать, кто повторяет неудавшийся этап и как убеждается, что предыдущая попытка не оставила частичный результат.
Индекс CONCURRENTLY тоже требует плана
CREATE INDEX CONCURRENTLY уменьшает блокирование записи по сравнению с обычным построением, но выполняет больше работы, может ждать другие транзакции и при сбое оставлять невалидный индекс. Эту команду нельзя выполнять внутри обычного блока транзакции. PostgreSQL 18: CREATE INDEX
Поэтому перед запуском нужно проверить поведение инструмента миграций: не оборачивает ли он весь файл в BEGIN/COMMIT. После запуска проверяют состояние конкретного индекса, а не только отсутствие ошибки у внешнего процесса. На репликах отдельно наблюдают отставание и нагрузку.
Как принять миграцию
Для учебного изменения полезна матрица из двух версий приложения и двух состояний схемы. Старое приложение должно пережить расширение, новое — работать до завершения заполнения, повторный запуск этапа — не портить уже обработанные данные.
Перед финальным удалением старого столбца проверяют реальные обращения: фоновые задания, отчёты, выгрузки и редкие административные команды. Один успешный веб-сценарий не доказывает, что старый контракт больше никому не нужен.
Для каждой стадии заранее определяют условия остановки: ожидание блокировки, рост задержки, нарушение данных, превышение допустимого отставания. Возврат предыдущей версии приложения возможен лишь пока схема и данные с ней совместимы. Файл с командой обратного переименования сам по себе такой совместимости не обеспечивает.