Миграции PostgreSQL без простоя: чек-лист перед релизом
Миграция может занять секунды, но заблокировать запись в таблицу на минуты — и этого хватит, чтобы очередь запросов выросла. Проверьте порядок изменений, блокировки, индексы и откат до того, как запускать DDL в продакшене.

В PostgreSQL 16 команда изменения схемы может быстро завершиться сама и всё же задержать приложение: например, если ей приходится ждать блокировку за длинной транзакцией. Перед релизом проверьте не только SQL, но и порядок выкладки приложения, размер бэкфилла, поведение при тайм-ауте и план отката.
Сначала проверьте порядок изменений
Безопасная миграция обычно не пытается за один релиз заменить старую схему новой. Она растягивает переход на несколько совместимых этапов: сначала база принимает оба варианта, затем приложение переводит данные и трафик, и только после этого удаляются старые поля или ограничения.
- Добавьте новое поле или структуру, не ломая старый код. Новую колонку обычно сначала создают допускающей NULL, без тяжёлого заполнения существующих строк в том же действии. Проверьте в документации PostgreSQL 16 особенности выбранного DDL: стоимость операции зависит от команды и значения по умолчанию.
- Выпустите приложение, совместимое со старой и новой схемой. Если нужно, временно записывайте данные в оба поля, а чтение переключите после проверки заполнения нового. Проверьте, что предыдущая версия приложения тоже переживёт этот этап.
- Перенесите старые данные небольшими порциями. Фиксируйте прогресс и ограничивайте нагрузку. Один большой UPDATE может долго удерживать блокировки, создать много WAL и раздуть таблицу; размер порции подбирайте по нагрузке и скорости репликации.
- Добавьте и проверьте ограничения. Для поддерживаемых PostgreSQL ограничений можно сначала создать constraint с
NOT VALID, а затем проверить существующие строки отдельнымVALIDATE CONSTRAINT. Убедитесь, что новые записи уже соответствуют правилу. - Переключите приложение и удалите старую схему отдельным релизом. До удаления проверьте чтение, запись, фоновые задачи, отчёты и доступность отката. Убедитесь, что старую колонку больше не использует ни одна активная версия кода.
Бэкфилл — массовое заполнение или преобразование уже существующих данных после изменения схемы. Его темп стоит контролировать отдельно от скорости самой миграции: быстрое завершение не всегда означает безопасную нагрузку на продакшен.
Сопоставьте способ изменения и его цену
Универсальной команды «без блокировок» нет. Выбирайте метод под конкретное изменение и оценивайте как длительность работы, так и блокировку, нужную для её старта.
| Изменение | Подход | Что проверить |
|---|---|---|
| Новая колонка | Сначала добавить nullable-поле, заполнить отдельно | Быстрая операция всё ещё должна получить блокировку; проверьте, не ждёт ли её уже открытая транзакция |
| Индекс на большой таблице | CREATE INDEX CONCURRENTLY | Операция дольше обычного создания индекса, требует дополнительной работы и не запускается внутри транзакционного блока |
| Новое ограничение | Добавить как NOT VALID, затем валидировать | Проверьте поддержку выбранного ограничения и обработку старых строк; не каждое ограничение проходит такой путь |
| Смена или удаление поля | Переходный релиз с поддержкой старой и новой схемы | Убедитесь, что старое поле больше не использует ни один экземпляр приложения или фоновая задача |
Индекс, созданный конкурентно, не блокирует обычные изменения таблицы так, как стандартная сборка, но это не бесплатная операция. Она может занять больше времени, конкурировать за ресурсы и оставить после ошибки недействительный индекс. Перед повторным запуском проверьте его состояние, а не считайте, что неудачная команда ничего не изменила.
Безопасная миграция — не та, что быстро прошла на копии базы. Это та, которую можно остановить, повторить и откатить без несовместимости приложения со схемой.
Проверьте блокировки и время ожидания
Многие изменения схемы запрашивают сильную блокировку. Даже если операция должна занять мало времени, она может ждать завершения транзакции, которая открыта давно. Пока запрос на блокировку ждёт, за ним могут накапливаться другие операции. Поэтому перед запуском проверьте долгие транзакции и активные запросы, а миграцию проводите в период, когда проще наблюдать за приложением.
Для DDL задайте ограничение на ожидание блокировки, чтобы миграция не висела бесконечно. Например, перед конкретной командой в отдельной сессии:
SET lock_timeout = '2s';
ALTER TABLE orders ADD COLUMN external_ref text;
Две секунды здесь — пример, а не готовый норматив для продакшена. Подберите значение по своим требованиям. Если блокировку не удалось получить, команда завершится ошибкой: это повод проверить обстановку и повторить запуск, а не автоматически увеличивать тайм-аут. Учтите, что настройка действует в текущей сессии; проверьте, как ваша система миграций использует соединения.
Отдельно задайте подходящий statement_timeout или убедитесь, что его не навязывает инфраструктура. Слишком короткий предел может оборвать корректный бэкфилл или построение индекса; слишком длинный — задержать реакцию на ошибку. Не запускайте длительное заполнение данных в одной транзакции вместе с изменением схемы.
Индексируйте так, чтобы не остановить запись
Для большой рабочей таблицы обычно рассматривают CREATE INDEX CONCURRENTLY. В PostgreSQL 16 эту команду нельзя выполнять внутри транзакционного блока. Проверьте, не оборачивает ли инструмент миграции каждый файл в транзакцию, и предусмотрите отдельный запуск для конкурентной сборки индекса.
CREATE INDEX CONCURRENTLY orders_external_ref_idx
ON orders (external_ref);
После запуска следите за длительностью операции, нагрузкой на диск, репликацией и журналами PostgreSQL. Если создание завершилось ошибкой, индекс может остаться в недействительном состоянии. Проверьте его перед повторной попыткой и включите очистку такого индекса в процедуру восстановления. Конкретный способ удаления тоже должен соответствовать ситуации и не выполняться вслепую.
Чек-лист перед продакшеном
- Зафиксирована версия PostgreSQL и проверена документация именно для неё. Примеры здесь относятся к PostgreSQL 16; для другой основной версии сверяйте синтаксис и поведение.
- Миграция проверена на копии данных, сопоставимой по размеру и распределению с рабочей базой. Маленькая тестовая таблица не покажет цену полного сканирования или бэкфилла.
- Проверена совместимость: старый код работает с новой схемой, а новый код не требует изменений, которые ещё не применены.
- Оценены длительность, блокировки, объём WAL и влияние на реплики. Для бэкфилла определены размер порции, пауза между порциями и критерий остановки.
- Для DDL заданы разумные тайм-ауты, а команда проверена в условиях, близких к продакшену.
- Понятно, как проверить результат: число необработанных строк, состояние ограничения или индекса, ошибки приложения и задержка репликации.
- Есть план остановки и отката. Отмена DDL не всегда возвращает данные в прежнее состояние, поэтому отдельно проверьте, что делать с уже перенесёнными строками и выпущенным кодом.
Где этот подход ломается
Переход по этапам не спасёт, если старое и новое приложение по-разному трактуют одно и то же значение. Проверьте, что будет при частично заполненной колонке, повторной обработке строки, сбое посередине бэкфилла и повторном запуске миграции. Если процесс нельзя безопасно повторить, заранее пропишите восстановление после каждого промежуточного состояния.
Не считайте добавление ограничения формальностью: проверка старых данных может обнаружить строки, которые нарушают новое правило. Подготовьте запросы для поиска таких записей и способ исправить их до включения ограничения. Для NOT NULL порядок действий и наличие подходящего проверенного constraint влияют на необходимость сканирования таблицы и блокировки; сверяйте конкретную команду с документацией PostgreSQL 16.
Также не удаляйте старую колонку в том же релизе, где приложение перестало её использовать. Отложенное удаление оставляет время на проверку метрик и откат к совместимой версии кода. Инвалидация кешей и согласование внешних потребителей — отдельные задачи; здесь важно не забыть о них в плане релиза.
Что делать дальше
Перед следующим релизом прогоните миграцию на копии базы и запишите время каждого этапа. В продакшене наблюдайте за ожиданием блокировок, длительностью запросов, ростом WAL, задержкой реплик и ошибками приложения. Если любой показатель выходит за заранее установленный порог, остановите бэкфилл или отложите следующий этап — не продолжайте только потому, что DDL уже запущен.
Тендерный специалист 44-ФЗ / 223-ФЗ: Разбор PDF-документации закупки, поиск «заточек» под поставщика, чек-лист заявки.
Спросить за 20 ₽Источники: PostgreSQL 16: ALTER TABLE, PostgreSQL 16: CREATE INDEX
Частые вопросы
Можно ли выполнить миграцию без блокировок вообще?
Почему CREATE INDEX CONCURRENTLY завершился ошибкой, но индекс остался?
Можно ли запустить CREATE INDEX CONCURRENTLY через обычный инструмент миграций?
Нужно ли добавлять колонку сразу с NOT NULL?
Что делать, если бэкфилл не успевает к релизу?
Как понять, что миграция опасна для продакшена?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.