Когда миграции базы данных требуют собственного пайплайна
У вас есть отлаженный CI/CD пайплайн для приложения. Код собирается, тесты прогоняются, деплои происходят автоматически. И тут кто-то открывает pull request, который добавляет новую колонку в таблицу users. Внезапно чистый пайплайн начинает казаться неправильным. Запуск миграции базы данных в том же потоке, что и код приложения, означает либо блокировку деплоя до завершения миграции, либо запуск миграции отдельно в надежде, что ничего не сломается.
Проблема в том, что изменения в базе данных ведут себя иначе, чем код приложения. Неудачная сборка означает лишь отсутствие нового деплоя. Неудачная миграция может повредить данные, заблокировать таблицы или оставить базу в несогласованном состоянии. Вам нужен пайплайн, спроектированный под специфические риски изменений схемы, обратного заполнения данных (backfill) и верификации.
Почему пайплайны приложений не подходят
Пайплайны приложений следуют простому шаблону: сборка, тестирование, деплой. Если тест падает — пайплайн останавливается. Если деплой не удался — вы чините проблему и деплоите заново. Откат обычно сводится к развертыванию предыдущей версии.
Миграции базы данных ломают эту модель. Миграция меняет структуру или содержимое ваших данных. Откат миграции — это не то же самое, что возврат изменения кода. Вам нужно запустить отдельный скрипт для отмены изменения схемы, и этот скрипт может не сработать, если данные уже были преобразованы. Также необходимо обработать обратное заполнение старых записей новыми значениями и проверить, что данные согласованы после миграции.
Попытки втиснуть изменения базы данных в тот же пайплайн, что и код приложения, ведут к компромиссам. Либо вы пропускаете автоматическое тестирование миграций, либо запускаете их вручную в окна обслуживания, либо принимаете риск запуска непроверенных скриптов в продакшене.
Отдельный пайплайн для изменений базы данных
Решение — создать выделенный пайплайн для изменений базы данных, полностью отдельный от пайплайна приложения. У этого пайплайна свои стадии, свои шлюзы утверждения и свой мониторинг. Он рассматривает изменения базы данных как полноценные деплои, а не как побочные эффекты релизов приложения.
Вот как стадии работают последовательно.
Следующий YAML-фрагмент показывает, как можно определить эти стадии в workflow GitHub Actions:
name: Database Migration Pipeline
on:
pull_request:
paths:
- 'migrations/**'
jobs:
dry-run:
runs-on: ubuntu-latest
steps:
- run: ./scripts/dry-run.sh
migration:
needs: dry-run
runs-on: ubuntu-latest
steps:
- run: ./scripts/migrate.sh
backfill:
needs: migration
runs-on: ubuntu-latest
steps:
- run: ./scripts/backfill.sh
reconciliation:
needs: backfill
runs-on: ubuntu-latest
steps:
- run: ./scripts/reconcile.sh
rollback-test:
needs: reconciliation
runs-on: ubuntu-latest
steps:
- run: ./scripts/rollback.sh
- run: ./scripts/reconcile.sh
Каждая задача запускается только в случае успеха предыдущей, отражая описанный выше поток пайплайна.
Следующая диаграмма показывает пять стадий и их последовательность:
Стадия 1: Dry-Run
Каждый раз, когда новый скрипт миграции попадает в ваш репозиторий, пайплайн запускает dry-run на staging-базе. Скрипт выполняется, но ничего не меняет. Цель — выявить синтаксические ошибки, отсутствующие зависимости или логические проблемы до того, как они коснутся реальных данных.
Если dry-run завершается неудачей, пайплайн немедленно останавливается. Команда получает уведомление, и никакие дальнейшие стадии не запускаются. Это отлавливает большинство распространенных ошибок на раннем этапе, когда их исправление дешево.
Стадия 2: Миграция
После успешного dry-run пайплайн запускает фактическую миграцию на staging-базе. Это изменяет схему или преобразует данные, но все еще в безопасной среде. Пайплайн логирует каждый шаг: время начала, время окончания, количество затронутых строк и любые предупреждения.
Эти логи служат аудиторским следом. Когда позже что-то идет не так, вы можете точно отследить, что произошло во время миграции. У вас также есть запись того, сколько времени занял каждый шаг, что помогает оценить время выполнения в продакшене.
Стадия 3: Backfill
Некоторые миграции требуют заполнения данных для существующих записей. Например, добавление новой колонки со значением по умолчанию может потребовать обновления миллионов существующих строк. Выполнение этого как одного массивного обновления может заблокировать таблицу на минуты или часы.
Пайплайн обрабатывает backfill небольшими пакетами, обычно по тысяче строк за итерацию, с короткой паузой между пакетами. Это сохраняет базу данных отзывчивой и снижает риск длительных блокировок. Пайплайн мониторит каждый пакет на предмет длительности и частоты ошибок. Если пакет завершается неудачей, пайплайн останавливается и отправляет оповещение. Он не делает автоматических повторных попыток, потому что сбой может указывать на более глубокую проблему, требующую расследования.
Стадия 4: Reconciliation
После завершения миграции и backfill пайплайн запускает скрипт сверки (reconciliation). Он сравнивает данные до и после миграции. Сравнение может проверять количество строк, контрольные суммы по определенным колонкам или агрегированные значения, такие как общий баланс в таблице транзакций.
Если сверка обнаруживает неожиданные расхождения, пайплайн завершается неудачей. Команда должна провести расследование перед продолжением. Эта стадия выявляет тихое повреждение данных, частичные обновления или логические ошибки, которые не вызвали сбой, но все равно привели к неверным результатам.
Стадия 5: Rollback Test
Пайплайн запускает скрипт отката, чтобы проверить, что миграция может быть чисто отменена. После отката он снова запускает сверку, чтобы подтвердить, что данные вернулись в исходное состояние.
Это самая важная стадия для укрепления уверенности. Если тест отката проходит на staging, вы знаете, что можете безопасно отменить миграцию в продакшене, если что-то пойдет не так. Если он не удается, пайплайн останавливается, и миграции не разрешается переходить в продакшен.
Запуск в продакшене
После того как все пять стадий пройдены на staging, пайплайн готов к продакшену. Но процесс не автоматический. Между staging и продакшеном находится шаг ручного утверждения. Кто-то со знанием базы данных просматривает результаты и утверждает запуск в продакшене.
В продакшене пайплайн выполняет ту же последовательность: dry-run, миграция, backfill, сверка и тест отката. Разница в том, что мониторинг более жесткий, и пайплайн может быть остановлен на середине стадии, если появляются аномалии. Каждая стадия в продакшене также имеет собственную возможность отката, так что вы можете прерваться в любой точке, не оставляя базу в сломанном состоянии.
Практический чек-лист для вашего пайплайна базы данных
- Отделите пайплайн базы данных от пайплайна приложения
- Всегда запускайте dry-run перед фактической миграцией
- Логируйте каждый шаг с временными метками и количеством строк
- Выполняйте backfill небольшими пакетами с мониторингом
- Добавляйте проверки сверки после миграции и backfill
- Тестируйте скрипты отката на staging перед продакшеном
- Требуйте ручного утверждения для продакшен-запусков
- Сохраняйте возможность остановить пайплайн на середине стадии
Вывод
Миграции базы данных заслуживают той же тщательности, что и деплои приложений. Выделенный пайплайн со стадиями dry-run, backfill, сверки и теста отката дает вам уверенность, что изменения схемы не сломают ваши данные незаметно. Дополнительные стадии добавляют времени каждой миграции, но они экономят гораздо больше времени, предотвращая инциденты в продакшене и последующую лихорадочную работу по восстановлению. Относитесь к изменениям базы данных как к продакшен-деплоям, и ваши данные будут оставаться согласованными при каждом обновлении.