SQL-миграция может пройти тесты на пустой базе и остановить запись на рабочей таблице. Синтаксис при этом останется корректным. До выкладки стоит проверять не только результат миграции, но и признаки опасного выполнения: обычное построение индекса, массовое изменение данных, отсутствие ограничения ожидания блокировки.
В статье Percona Community независимый разработчик Yi предлагает узкую предварительную проверку: разбирать SQL до запуска и останавливать CI там, где риск виден из самого текста. Ниже — адаптация этого подхода для инженерной команды. Проверка помогает ревьюеру, но не заменяет знание рабочей базы.
Начните с правил, которые можно объяснить
Первый набор правил не обязан понимать весь PostgreSQL. Достаточно находок, для которых понятно, что именно нужно проверить человеку: создание индекса без CONCURRENTLY, удаление таблицы, TRUNCATE, изменение типа столбца, UPDATE или DELETE без условия. Отдельное правило проверяет, задан ли ненулевой lock_timeout перед защищаемой операцией.
У каждой находки должна быть причина. Сообщение «опасная миграция» почти бесполезно. Сообщение «обычное построение индекса блокирует запись; проверьте допустимое окно и режим запуска» уже направляет ревью. Полезно показывать оператор, сработавшее правило и вопрос, который нужно закрыть до слияния.
Строгость правила не равна доказанному масштабу последствий. Анализатор без доступа к каталогу может отправлять на ручное рассмотрение любое изменение типа. Это консервативная политика команды, а не утверждение, что каждая такая операция обязательно перепишет всю таблицу. Не стоит превращать удобную эвристику в неверную документацию о поведении базы.
Таймаут должен стоять перед операцией
Проверка простого наличия lock_timeout в файле недостаточна. Если сначала выполняется ALTER TABLE, а затем настройка таймаута, первая операция уже прошла без этой защиты. Анализатору нужен порядок операторов и состояние настройки на каждом шаге. Сброс значения в ноль тоже нужно учитывать: он отключает ограничение.
При этом lock_timeout ограничивает ожидание получения блокировки, а не полную длительность работы после её получения. Поэтому вопрос «сколько миграция может ждать» следует отделять от вопроса «сколько она может выполняться». Для второго служит statement_timeout; если он не больше ограничения ожидания, именно он может сработать раньше. Эти различия описаны в документации PostgreSQL 18.
Универсального значения для всех таблиц нет. Команда должна выбрать пределы под допустимое влияние на приложение и проверить их на репетиции. Копирование числа из чужого примера даст формально зелёную проверку, но не объяснит, что произойдёт при реальной конкуренции за блокировки.
Индекс проверяйте вместе с механизмом миграций
Обычный CREATE INDEX позволяет чтение, но блокирует изменения строк на время построения. Вариант CONCURRENTLY снимает эту конкретную помеху записи, однако требует больше работы и может ждать другие транзакции. Это компромисс, а не бесплатное ускорение.
Есть и ограничение на запуск: CREATE INDEX CONCURRENTLY нельзя выполнять внутри транзакционного блока. Отсутствие BEGIN в файле ничего не доказывает, если библиотека миграций сама оборачивает файл в транзакцию. Ревью должно охватывать и SQL, и настройки исполнителя. Документация CREATE INDEX также описывает невалидный индекс, который может остаться после неудачного конкурентного построения.
Отсюда практический запрет на автоматическое «исправление»: линтер не должен просто добавлять CONCURRENTLY. Такая замена меняет требования к выполнению и восстановлению. Сначала разработчик выбирает сценарий, затем проверяет его вместе с ревьюером. После сбоя нужно выяснить фактическое состояние индекса, прежде чем повторять миграцию.
Зелёный результат имеет узкий смысл
Условие WHERE не гарантирует маленькое изменение. Оно может выбрать почти всю большую таблицу. Правило «нет WHERE» ловит очевидный случай, но наличие предиката не сообщает ни число затронутых строк, ни объём будущей работы.
То же относится к таймаутам и индексам. Набор проверок может не найти нарушений, хотя миграция создаст существенную нагрузку. В интерфейсе CI результат лучше формулировать буквально: «блокирующих находок нет». Формулировка «безопасно для production» обещает то, чего статический анализ не установил.
Для каждой проверки полезно явно записать границы. Она видит текст операторов, но не текущих держателей блокировок, распределение данных, свободную ёмкость под WAL или отставание реплик. Такое описание помогает ревьюеру продолжить анализ с нужного места, а не повторять работу линтера.
Разбирайте структуру SQL и сохраняйте ошибки
В исходном эксперименте SQL преобразуется в абстрактное синтаксическое дерево. Это позволяет различать действия внутри операторов и последовательно учитывать настройки сессии. Регулярное выражение, которое ищет отдельные слова, хуже подходит для такой задачи.
Однако сторонний парсер может не поддерживать допустимый синтаксис PostgreSQL. Его отказ означает пробел в покрытии анализатора, а не ошибку серверного SQL. Такой результат нельзя молча превращать в успешную проверку: миграция остаётся непроверенной и требует отдельного разбора.
Для команды это ещё и материал для развития инструмента. Сохраняйте минимальный пример неподдержанного синтаксиса, добавляйте проверку после исправления парсера и не смешивайте эту категорию с реально найденным опасным оператором. Иначе статистика срабатываний будет показывать шум вместо полезных ограничений.
Внедряйте проверку постепенно
Начните с небольшого набора понятных правил и посмотрите, как они работают на ваших миграциях. Автор предлагает измерять ложные срабатывания прежде, чем делать более широкие организационные ограничения блокирующими. Если каждое второе изменение требует обхода, команда быстро перестанет читать сообщения.
Разделяйте отсутствие находок, найденное нарушение и ошибку запуска самого анализатора. CI должен различать эти состояния, чтобы сломанная проверка не выглядела успешной. Машиночитаемый отчёт удобен для комментариев к изменённым строкам, но рядом всё равно нужна понятная человеку причина остановки.
Для значимых изменений после статической проверки нужна репетиция на сопоставимом объёме данных. Проверьте ожидаемые сканирования, влияние на приложение, блокировки и репликацию. Заранее определите условия остановки и порядок восстановления. После выполнения подтвердите состояние созданных объектов, а не только успешное завершение процесса миграций.
Предварительная проверка полезна именно своей ограниченностью: она убирает из ревью ошибки, которые можно заметить без доступа к production. Освободившееся внимание стоит потратить на размер данных, конкуренцию и восстановление — вопросы, на которые один SQL-файл ответить не может.
Источник и лицензия
Автор: Yi. Корпоративный блог: Percona Community, Percona. Исходная статья: A Fail-Fast PostgreSQL Migration Preflight for CI, 8 сентября 2026 года.
Материал опубликован по CC BY 4.0, согласно условиям Percona Community Writers Program. Это самостоятельная русскоязычная адаптация с сокращением примеров и редакционной переработкой структуры. Автор и Percona не заявляли об одобрении этой публикации.