Настройка автоматического тестирования миграций БД в CI/CD

Выкатка падает из-за изменений схемы, а узнаёте вы об этом уже на проде. Мы настраиваем автоматическое тестирование миграций БД под ключ: проверки в конвейере ловят сбой до выпуска. Время на согласование и откаты сокращается в разы.
Часто задаваемые вопросы

Автоматическое тестирование миграций БД: где ломается выкатка

Сбой на выкатке редко случается в самой миграции — он зреет в расхождении схемы. Боевая база и репозиторий описывают разные состояния: изменение когда-то применили вручную, чтобы разблокировать релиз, в цепочку миграций не зафиксировали — и следующая выкатка падает на несуществующем объекте либо создаёт дубль. Проверка изменений схемы устроена как сравнение слепка боевой базы с результатом прогона миграций с нуля: без неё расхождение всплывает только в момент сбоя.

Вторая точка отказа — блокировка. Изменение типа колонки или перестройка индекса на большой таблице держит блокировку минутами: запись встаёт, очередь соединений растёт, сервис деградирует. На пустом тестовом стенде такое изменение проходит мгновенно, поэтому решение о выкатке принимают вслепую. Прогон на копии боевых данных показывает, сколько держится блокировка и переживёт ли её пиковая нагрузка.

Третья — откат изменения, который никто не проверял. Обратные скрипты пишут по остаточному принципу и не гоняют в конвейере. При откате релиза код возвращается к прежней версии, а схема остаётся новой: запросы падают на несовпадении типов, и восстановление затягивается дольше, чем длилась сама выкатка.

Закрывается это циклом: применить миграции на копии боевых данных, откатить, применить снова и сверить итоговую схему с эталонным слепком. Тогда необратимое изменение видно до прода, а не после.

Что даёт проверка изменений схемы до выкатки: время и класс отказов

Пока изменения схемы проверяются руками на копии боевой базы, каждая выкатка содержит паузу: инженер ждёт, пока накатится набор миграций, и только потом смотрит, поднялось ли приложение. На потоке из нескольких релизов в неделю это часы ожидания и отдельный класс отказов — «упало на схеме», который ловится уже после переключения трафика.

Автоматический прогон миграций в конвейере закрывает это на этапе сборки. Изолированное окружение базы разворачивается из слепка на той же версии PostgreSQL, что и боевая: накат всех миграций по порядку, откат до предыдущего состояния и повторный накат. Время выпуска перестаёт зависеть от того, кто именно сегодня дежурит.

Дальше проверяются свойства, которые руками почти не тестируют. Обратная совместимость схемы — приложение предыдущей версии продолжает работать с новой схемой, значит откат релиза не требует отката миграции. Идемпотентность миграций — повторный запуск даёт тот же результат, и упавшую на середине миграцию можно безопасно прогнать снова.

Отдельная проверка — простой из-за блокировок: тест замеряет, сколько держится исключительная блокировка при изменении таблицы, и помечает операции, останавливающие запись на живой базе. Такие шаги разбиваются на безопасные: добавить колонку, заполнить фоном, переключить чтение, удалить старое.

Итог измерим: отказы, связанные со схемой, уходят из статистики выпусков, повторные прогоны не требуют ручного вмешательства, а предсказуемость выпуска перестаёт зависеть от конкретного инженера.

Подходы к прогону изменений схемы: пустая база, копия, реплика

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

Поэтому в CI/CD обычно держат не один вариант, а несколько — под разные стадии и разный уровень риска.

Вариант Когда подходит Ограничения
Прогон на пустой базе На каждый коммит: проверка синтаксиса, порядка применения и отката Не воспроизводит конфликты с существующими данными и объёмные перестроения таблиц
Упрощённая база с репрезентативной выборкой Быстрые проверки, где нужны данные, но полная копия слишком тяжёлая Расхождение с боевой схемой накапливается: тесты проходят там, где реальная выкатка упадёт
Копия боевой схемы с обезличенными данными Предрелизная проверка перед выкаткой на критичный контур Нужны снимок, обезличивание и регулярная актуализация; подготовка занимает время
Теневая реплика Оценка длительности блокировок и поведения на реальном объёме и потоке Требует изоляции от боевого трафика и отдельных ресурсов, дороже в поддержке

Комбинируем по возрастанию цены и достоверности: пустая база на каждый коммит, копия — на ветке перед выкаткой, теневая реплика — точечно для миграций, которые трогают большие таблицы или меняют типы столбцов. Упрощённая база полезна как быстрый компромисс, но её нужно периодически сверять с боевой схемой — иначе тесты начинают подтверждать то, чего в реальности нет.

Как выстроить проверку миграций в конвейере: этапы работ

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

  1. Инвентаризация миграций. Собираем накопленные файлы, отделяем идемпотентные от одноразовых и помечаем опасные: смену типа колонки, перестройку крупной таблицы, удаление столбцов.

  2. Эталонная схема и версионирование схемы. Снимаем дамп актуального состояния и храним его вместе с кодом — каждая миграция обязана приводить структуру к эталону. Появляется детерминированная проверка вместо «на тестовой базе вроде работало».

  3. Чистые окружения в конвейере. Поднимаем пустую базу нужной ветки и прогоняем два сценария: накат всех миграций с нуля и обновление с состояния прошлого релиза.

  4. Автотесты на схему и данные. Сравниваем структуру с эталоном, проверяем обратимость откатных миграций и целостность данных на подготовленном наборе. Красный тест блокирует сборку до исправления.

  5. Проверка блокировок под нагрузкой. Прогоняем миграцию на объёме, близком к рабочему, и снимаем план выполнения запроса: сколько держится блокировка, не перестраивается ли индекс целиком, укладывается ли операция в окно обслуживания.

  6. Регламент. Миграция попадает в релиз только с зелёными проверками, а накат в рабочий контур вручную — через отдельную заявку с фиксацией причины.

  7. Передача. Отдаём описание конвейера, порядок дежурства и разбор типовых сбоев, чтобы команда сопровождала схему дальше сама.

Что заказчик получает на руки: конфигурации, тесты, регламенты

Работа заканчивается не отчётом, а комплектом, который ваша команда запускает и поддерживает без нас. Всё, что настроено на тестовом контуре, передаётся в том же виде, в котором работает на основном, — с описанием, почему выбраны такие пороги и как их менять.

  • Настройки конвейера непрерывной поставки: шаги сборки, запуск миграций на пустой базе и на копии боевых данных, условие остановки выпуска при расхождении схемы.
  • Набор автотестов схемы: применение и откат каждой миграции, сверка таблиц, индексов, ограничений, значений по умолчанию и прав после наката.
  • Настройки окружений и скрипты подъёма/сброса: разворот базы за считанные минуты, предсказуемые тестовые данные, сброс без остаточных объектов между прогонами.
  • Политики доступа к копиям: кому и на какой срок выдаются обезличенные копии, где они хранятся, как отзываются права и что попадает в журнал.
  • Регламент выпуска: действия при конфликте миграций, правила совместимости соседних версий схемы, точка отката и ответственный за решение.
  • Комплект документации: карта конвейера, описание окружений, перечень проверок с примерами падений, порядок разбора инцидентов.

Отдельно фиксируем, как добавить новую проверку или окружение своими силами: обычно это копия существующего блока с изменёнными параметрами. Такой комплект снимает зависимость от подрядчика — развивать тестирование миграций можно внутри команды.

Кейс: как схема перестала ломать выкатку после внедрения проверок

До: схема расходилась между средами

Миграции выпускались тем же конвейером, что и код, но проверялись глазами: ревьюер читал текст миграции и сверял его с текущей схемой. Тестовый стенд догонял прод вручную и не всегда. Из десяти релизов два-три падали на применении — блокировка таблицы на большом объёме данных, конфликт с уже существующим индексом, снятая колонка, которую ещё читал предыдущий релиз кода. Дрейф схемы копился месяцами: на тестовом стенде не хватало части полей, а на проде жили индексы, которых не было ни в одном репозитории.

Что изменили

Каждую миграцию прогоняем дважды: на пустой базе — проверка применимости с нуля, и на свежей копии прод-схемы — проверка совместимости с накопленными данными. Обратный сценарий обязателен: без рабочего отката миграция не проходит стадию. Порядок изменений держим в журнале Liquibase, поэтому откат шага назад — такая же автоматическая операция, как и применение. Снимки схемы из dev, тестового стенда и прода сравниваются на каждой сборке: расхождение блокирует выкатку с указанием конкретного объекта.

Что стало

Время выпуска сократилось с двух-трёх дней до нескольких часов: стадия проверки идёт параллельно сборке и не требует ручного прогона перед релизом. Отказов на выкатке — один за квартал вместо двух-трёх из десяти релизов. Расхождений между средами было больше тридцати объектов, стало ноль — проверка не пропускает их дальше сборки. Разбор инцидентов на этапе изменения схемы ушёл из практики: конфликт виден на стадии проверки, а не на боевой базе под нагрузкой.

Чем прогон на пустой базе отличается от прогона на копии боевой?

Пустая база проверяет только то, что миграция синтаксически корректна и применяется в нужном порядке: таблицы создаются, индексы встают, внешние ключи валидны. Достоверность такой проверки заканчивается там, где начинается поведение на данных. Планировщик запросов на пустых таблицах выбирает один план, а на боевых объёмах — другой: обход индекса, незаметный на десятке строк, на реальном распределении даёт блокировки и рост времени отклика.

Копия боевой закрывает этот разрыв: срез с сохранённым объёмом тестовых данных и распределением значений — редкие ветки, пустые значения, дубликаты, строки предельной длины — заставляет ограничения работать по-настоящему. Ограничение целостности срабатывает только тогда, когда в таблице есть строка, которую оно должно отклонить; на пустой базе этот сценарий не воспроизводится.

Отдельная причина расхождений — диалект базы. На другом диалекте не воспроизводится приведение типов, сравнение строк с учётом регистра, порядок сортировки и обработка пустых значений. Миграция проходит в конвейере и падает уже на боевой, когда откат стоит дороже всего.

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

Сомнения инженера: сложные доработки схемы, дрейф, откаты

Возражения против автотестов миграций обычно предметные: схема уже живёт в проде, часть данных под ограничениями, а изменение может держать таблицу часами. Ниже — как каждый из этих случаев проверяется на стенде, а не в бою.

Схема в проде уже разошлась с миграциями — тесты это покажут?

Стенд поднимается из накатанной цепочки миграций, и расхождение структуры с продом видно на первом же прогоне — до выката, а не после. Дальше расхождение либо закрывают новой миграцией, либо приводят прод к цепочке отдельным скриптом, который тоже проходит проверку.

Изменение схемы идёт долго и блокирует таблицу — что с этим делают?

Прогон на копии продового объёма показывает, сколько реально держится блокировка. Если дольше допустимого окна — изменение разбивают на шаги: добавить колонку без ограничения, заполнить порциями, переключить чтение, снять старое. Каждый шаг — отдельная миграция со своим тестом.

Как проверяется обратная совместимость и возможен ли откат?

Тесты поднимают рядом прошлую и новую версию приложения и проверяют, что обе работают на одной схеме — тогда откат выката не требует отката базы. Обратный скрипт прогоняется на данных, оставшихся после прямого; если откат без потери невозможен, это фиксируется на согласовании заранее.

Данные чувствительные — как тестировать, если их нельзя копировать?

Стенд получает синтетический набор с той же структурой и похожим распределением значений, поэтому ограничения внешних ключей и уникальности воспроизводятся полностью. Чувствительные поля заменяют генераторами — проверка идёт на реальных правилах, а не на пустых таблицах.

Обсудим вашу задачу: что уточним и с чем вернёмся

Чтобы вернуться с оценкой и планом, а не с общими соображениями, нужен разбор вашего конвейера: как правки схемы проходят путь от изменения в ветке до релиза и что происходит между этими точками. Ниже — вводные, которых нам обычно хватает, чтобы посчитать объём работ.

  • СУБД и инструмент миграций: чем накатываются изменения, версионируются ли они пофайлово, есть ли транзакционная обёртка и отдельный шаг отката.
  • Объём и характер правок: сколько изменений схемы в неделю, есть ли тяжёлые перестроения больших таблиц и блокирующие операции.
  • Что уже проверяется: ручные прогоны на копии, ревью запросов, самописные скрипты — и на каком шаге они дают сбой.
  • Окружения и данные: есть ли отдельный контур для прогонов, как готовится тестовый набор, обезличиваются ли данные.
  • Ограничения: допустимое окно недоступности, требования к откату, порядок согласования релизов.
  • Доступы и ответственность: кто владелец репозитория, раннеров и настроек окружений, кто примет регламенты на стороне эксплуатации.

После разбора вернёмся со схемой конвейера, оценкой и планом работ, а по итогам внедрения передадим регламенты: что запускается на каждое изменение, какие проверки блокируют слияние, как действовать при падении. Дальше остаёмся на сопровождении — донастраиваем правила под новые типы миграций и разбираем инциденты. Опишите задачу — вернёмся с оценкой и планом.

Наши компетенции:

Последние работы

  • Разработка сайта компании B2B ADVANCE
    Разработка сайта компании B2B ADVANCE
    1542
  • Разработка веб-приложения для компании FEEDME
    Разработка веб-приложения для компании FEEDME
    1394
  • Разработка веб-сайта для компании БЕЛФИНГРУПП
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    1081
  • Разработка интернет магазина для компании FURNORO
    Разработка интернет магазина для компании FURNORO
    1369
  • Разработка логотипа компании B2B Advance
    Разработка логотипа компании B2B Advance
    816
  • Разработка веб-приложения для компании Enviok
    Разработка веб-приложения для компании Enviok
    1141