Миграции базы данных: что проверить перед выпуском
Чек-лист, который стоит пройти до нажатия кнопки deploy
Введение: почему миграции ломают продакшн
Упасть на деплое кода — неприятно. Упасть на миграции базы данных — это чаще всего несколько часов даунтайма, потенциальная потеря данных и ночные созвоны. В отличие от кода, схема базы — это состояние. Откатить её не всегда возможно, а иногда откат сам по себе становится источником потери данных [1].
Инженер Devrim Özcay описал это точно: в апреле 2026 года он добавил NOT NULL-ограничение на таблицу с 84 миллионами строк. На стейджинге с 10 000 строк миграция заняла 8 секунд. На продакшне PostgreSQL захватил ACCESS EXCLUSIVE LOCK и не отпускал его 4 часа — все запросы к таблице users вставали в очередь и падали с таймаутом. API возвращал 503. Пул соединений был исчерпан [2].
Это не экзотика. Согласно данным Harness, 72% организаций пережили минимум один production-инцидент, вызванный кодом, написанным или изменённым с помощью AI [3]. Миграции — именно тот тип кода, который читают хуже всего и который AI-ассистенты генерируют охотнее всего.
В этой статье — практический разбор того, что нужно проверить перед выпуском миграции: от блокировок таблиц до секретов в скриптах, от rollback-плана до поведения в микросервисной среде.
Часть 1. Чем миграция отличается от обычного деплоя
Код можно откатить, переключив версию в CI/CD. Схему базы данных — нет. Если колонка удалена, данные из неё исчезли. Если тип изменён с потерей точности — оригинальные значения не восстановить [4].
Именно эта несимметричность и делает миграции особенным классом изменений. Вот три свойства, которые требуют отдельного внимания:
- Постоянное состояние. База хранит данные между деплоями, между версиями кода, между инцидентами. Миграция меняет это состояние — часто необратимо.
- Временной разрыв. В системах с нулевым даунтаймом старый и новый код некоторое время работают одновременно с одной схемой. Если схема несовместима с предыдущей версией кода, старые инстансы начинают падать ещё во время деплоя.
- Блокировки. DDL-операции в большинстве СУБД требуют эксклюзивных блокировок. Пока блокировка удерживается — все остальные запросы к таблице ждут. В PostgreSQL даже обычный
SELECTвстаёт в очередь за DDL-оператором, если тот ещё не получил свою блокировку [5].
Понять это — значит иметь правильную рамку для всего последующего чек-листа.
Часть 2. Блокировки таблиц: самая частая причина инцидентов
Как работают DDL-блокировки в PostgreSQL
Любой ALTER TABLE требует ACCESS EXCLUSIVE LOCK — самого строгого типа блокировки. Он несовместим абсолютно со всеми другими блокировками, включая ACCESS SHARE, которую захватывает обычный SELECT [5].
Инцидент развивается по следующей схеме. Если в момент запуска миграции есть хотя бы один долго выполняющийся запрос к целевой таблице — DDL становится в очередь. Пока DDL ждёт, все новые запросы к этой таблице тоже встают за ним в очередь. Приложение начинает деградировать. Пул соединений исчерпывается. API возвращает 500/503. Обычно всё это занимает не больше нескольких минут, но производительность рушится раньше [6].
Пять паттернов PostgreSQL-миграций, которые чаще всего вызывают production-инциденты [7]:
CREATE INDEXбезCONCURRENTLY— захватываетSHARE LOCKна всё время построения индекса, блокируя все write-операции.ALTER TABLE ADD COLUMN ... NOT NULL DEFAULT <значение>— до PostgreSQL 11 это вызывало полную перезапись таблицы; в новых версиях стало безопаснее, но только если значение по умолчанию не volatile.ALTER COLUMN TYPE— если новый тип требует другого бинарного представления, PostgreSQL перезаписывает каждую строку; на большой таблице это часы.ADD CONSTRAINTбезNOT VALID— PostgreSQL сканирует все строки для проверки; безопасная альтернатива — добавить сNOT VALID, затемVALIDATE CONSTRAINTв фоне.- Отсутствие
lock_timeout— DDL может ждать блокировку бесконечно, блокируя при этом все входящие запросы.
Что проверять перед запуском
Перед каждой DDL-миграцией нужно ответить на следующие вопросы:
- Установлен ли
lock_timeoutв сессии миграции? Значение менее 2 секунд — стандартная практика [8]. Это гарантирует, что DDL не будет блокировать остальные запросы дольше допустимого. - Есть ли долго выполняющиеся транзакции на целевой таблице? Нужно проверить
pg_stat_activityперед запуком. - Используется ли
CREATE INDEX CONCURRENTLYдля всех новых индексов? Это фундаментальное требование для продакшна. - Добавляется ли
NOT NULL-ограничение черезNOT VALID→VALIDATE CONSTRAINT? - Если меняется тип колонки — нужен expand/contract, а не прямой
ALTER COLUMN TYPE.
Часть 3. Паттерн Expand → Backfill → Contract
Это фундаментальный подход к ломающим изменениям схемы, позволяющий выкатывать их без даунтайма [9].
Суть паттерна — разделить опасное изменение на три независимых деплоя:
Фаза Expand (расширение). Добавляем новую структуру рядом со старой, ничего не удаляя. Если нужно переименовать колонку user_name в username — добавляем username как отдельную колонку. Старый код продолжает работать без изменений, так как видит только user_name.
Фаза Backfill (заполнение). Копируем данные из старой структуры в новую. Для больших таблиц — батчами по 1 000–10 000 строк с паузами между батчами, чтобы не создавать I/O-насыщение. Добавляем триггеры или dual-write в приложении, чтобы новые записи попадали в обе колонки одновременно.
Фаза Contract (свёртка). После того как новая колонка полностью заполнена и новый код деплоен и проверен — удаляем старую структуру. В большинстве современных баз этот ALTER TABLE DROP COLUMN — операция на метаданных, выполняется за миллисекунды независимо от размера таблицы [10].
Паттерн применим к переименованию колонок, изменению типов, переносу данных между таблицами. Он чуть дольше простого ALTER TABLE, но принципиально меняет профиль риска: каждая из трёх фаз — независимо обратима [11].
В инструментах это реализовано, например, в pgroll — open-source-инструменте для PostgreSQL, автоматизирующем expand-contract по умолчанию [12].
Часть 4. Обратная совместимость и многоверсионное развёртывание
В системах с rolling deployments или микросервисами одновременно могут работать несколько версий одного сервиса. Это значит, что схема базы данных должна быть совместима с обеими версиями кода — до завершения полного роллаута [13].
Практическое правило: миграция применяется до деплоя нового кода. Это значит, что пока новый код ещё не запущен, схема уже изменилась, и старый код должен продолжать работать на новой схеме. Именно поэтому:
- нельзя удалять колонки, которые ещё читает старый код;
- нельзя добавлять
NOT NULL-ограничения без дефолтного значения на существующие строки, пока старый код не умеет это значение проставлять; - нельзя переименовывать колонки в один шаг — старый код не знает нового имени.
Стандартная схема безопасного деплоя [14]:
- Деплой миграции (только backward-compatible изменения — добавление колонок, индексов, таблиц).
- Деплой нового кода (теперь он пишет в обе структуры).
- Верификация: проверка, что новый код работает корректно.
- Отдельный деплой cleanup-миграции (удаление старой структуры).
Для Kubernetes-окружений отдельно стоит проверить, что миграция не запускается при старте каждого пода параллельно. Нужна защита от параллельного запуска (advisory lock или механизм миграционного инструмента) [15].
Часть 5. Rollback: что на самом деле можно откатить
Rollback базы данных — одна из самых переоценённых концепций в практике деплоев. Большинство инструментов поддерживают откат схемы, но не откат данных. Именно там начинается настоящая проблема [4].
Специалисты SchemaSmith формулируют это прямо: если миграция слила две колонки в одну, удалила таблицу после переноса данных в другую или изменила тип с потерей точности — ни один инструмент не откатит это автоматически без backup исходных данных [16].
Перед каждой production-миграцией нужно письменно ответить на шесть вопросов [16]:
- Что изменяет эта миграция: только схему, только данные или и то, и другое?
- Если откат нужен — какие данные, записанные после миграции, будут потеряны?
- Сколько времени займёт откат: секунды (revert скрипт), минуты (down-migration) или часы (полное восстановление из backup)?
- Есть ли актуальный backup, снятый непосредственно перед запуском миграции?
- Кто принимает решение об откате и кто технически его исполняет?
- Проверен ли rollback-скрипт в стейджинге до начала production-деплоя?
Если хотя бы на один вопрос нет чёткого ответа — миграция не готова к выпуску.
Стратегия fix-forward (вместо rollback) часто предпочтительнее для изменений данных: вместо попытки откатить состояние — быстро деплоим корректирующую миграцию. Она проще, предсказуемее и не создаёт риска потери данных, которые были записаны между деплоем и откатом [17].
Часть 6. Объёмы данных: главный разрыв между стейджингом и продакшном
Самая распространённая причина неожиданных инцидентов при миграции — тестирование на маленькой базе. На 10 000 строк всё быстро. На 84 миллионах строк то же самое не работает [2].
Конкретные примеры расхождений [18]:
CREATE INDEXна таблице с 100 тысячами строк — 2 секунды. На 100 миллионах — до 30 минут с блокировкой всех write-операций, если использовался обычный (неCONCURRENTLY) индекс.ALTER TABLE ... ADD CONSTRAINT CHECKсNOT VALID— мгновенно. Та же операция безNOT VALID— полный scan таблицы, время пропорционально количеству строк.- Backfill батчами: на миллионе строк с паузами 100 мс займёт несколько минут. На миллиарде — часы или сутки.
Что проверять:
- Сколько строк в целевой таблице на продакшне? Это нужно знать точно, а не примерно.
- Тестировалась ли миграция на данных, сопоставимых по объёму с продакшном?
- Если стейджинг значительно меньше — использовались ли нагрузочные тесты на синтетических данных?
- Учтён ли replication lag? При наличии read-реплик индекс-миграция на первичной базе создаёт replag на репликах во время деплоя [14].
Хорошая практика — иметь стейджинг с объёмом данных, сопоставимым с продакшном, хотя бы для критических таблиц. Для PostgreSQL это достижимо через периодическое восстановление production-бэкапа в стейджинг-среду с маскированием чувствительных данных.
Часть 7. Безопасность миграционных скриптов
Миграции хранятся в репозитории. Репозитории читаются людьми, CI/CD-системами и иногда — утекают. Это создаёт специфический класс уязвимостей.
Секреты в теле скриптов
Hardcoded credentials — одна из самых критичных проблем безопасности в разработке. По данным GitGuardian, в 2025 году в публичные GitHub-репозитории было добавлено 28,65 миллиона новых захардкоженных секретов — рост 34% год к году [19]. OWASP в своём списке Non-Human Identities Top 10 ставит Secret Leakage на второе место по критичности [20].
Миграции особенно подвержены этой проблеме по ряду причин:
- В них часто встречаются одноразовые скрипты, написанные быстро под давлением дедлайна.
- Они могут содержать
INSERT-операции с тестовыми данными, где случайно оказались реальные credentials. - AI-ассистенты при генерации миграций иногда включают placeholder-значения, которые разработчики заменяют реальными и коммитят [19].
Что проверять перед merge:
- Нет ли в тексте миграции строк с паролями, токенами, API-ключами, connection strings с credentials?
- Если скрипт использует переменные окружения — они действительно подставляются из environment, а не из файла
.env, закоммиченного в репозиторий? - Прогнан ли secret scanner (TruffleHog, Gitleaks, GitHub Secret Scanning) на diff перед merge?
Права доступа и принцип минимальных привилегий
Пользователь, от имени которого выполняется миграция, не должен иметь привилегий, избыточных для конкретной задачи [21]. Если миграция только добавляет колонку — ей не нужны права на DROP TABLE. Этот принцип особенно важен в CI/CD, где миграция запускается автоматически.
Sensitive data в migration-файлах
Отдельный риск — seed-данные или тестовые данные в migration-скриптах, содержащие реальные PII. При переезде в новое окружение или при публикации репозитория такие данные становятся доступными. Sensitive data должны находиться в отдельных, защищённых seed-файлах и никогда — в миграционных скриптах вместе со schema changes.
Часть 8. Версионирование и инструменты
Любое изменение схемы должно быть версионировано — с тем же уровнем дисциплины, что и application code. Это не просто best practice, это основа возможности аудита, дебага и корректного rollback [22].
Два наиболее распространённых инструмента:
Flyway — простой, основан на линейной нумерации файлов (V1__..., V2__...), хорошо интегрируется в CI/CD. Работает на принципе forward-only по умолчанию (версия Community). Rollback поддерживается только в Enterprise-версии через отдельные undo-скрипты [23].
Liquibase — более гибкий, поддерживает XML, YAML, JSON и SQL. Позволяет определять rollback-операции для каждого changeset. Поддерживает diff-операции для сравнения сред [23].
Оба инструмента решают одну задачу: гарантия того, что каждая миграция применяется ровно один раз на каждой среде, в правильном порядке, с логированием факта применения.
Что проверять при использовании инструментов версионирования:
- Нет ли конфликтов версий (два разработчика создали файл с одинаковым номером в разных ветках)?
- Не изменён ли уже примененный файл миграции — это типичная ошибка, приводящая к расхождению checksums и отказу инструмента?
- Соответствует ли порядок применения миграций ожиданиям?
- Для Flyway: используется ли
repairтолько осознанно и с пониманием, что именно он исправляет?
Часть 9. Чек-лист перед выпуском
Это минимальный набор вопросов, который стоит пройти перед каждым production-деплоем, содержащим миграцию.
Корректность и безопасность скрипта:
- Миграция проверена на наличие секретов и credentials.
- Права, под которыми запускается миграция, соответствуют принципу минимальных привилегий.
- В коде нет PII или sensitive data в явном виде.
- Migration-файл не изменялся после первого применения на любой среде.
Обратная совместимость:
- Миграция совместима со старой версией application code — если деплой rolling, оба кода будут работать одновременно.
- Ни одна колонка или таблица, используемая текущей версией кода, не удаляется и не переименовывается в этом деплое.
- Ломающие изменения декомпозированы по паттерну expand → backfill → contract на несколько отдельных деплоев.
Блокировки и производительность:
- Для всех новых индексов используется
CREATE INDEX CONCURRENTLY(PostgreSQL) или эквивалент. lock_timeoutустановлен для DDL-сессии.NOT NULL-ограничения добавляются черезNOT VALID→VALIDATE CONSTRAINT.- Время выполнения миграции проверено на данных, сопоставимых по объёму с продакшном.
- Учтён replication lag при наличии read-реплик.
Rollback:
- Определён план отката: что делать, если миграция упала на полпути.
- Rollback-скрипт написан и проверен на стейджинге.
- Актуальный backup снят непосредственно перед запуском production-деплоя.
- Определено время исполнения отката и лицо, принимающее решение.
CI/CD и процесс:
- Миграция прошла через code review с явной проверкой на опасные паттерны.
- На стейджинге миграция применялась чисто, без ошибок.
- Нет параллельного запуска миграции на нескольких нодах (advisory lock или аналог).
- Monitoring настроен: есть алерты на lock wait, длину очереди соединений, replication lag.
Часть 10. Миграции в эпоху AI-кодинга
В 2025–2026 годах большая часть нового кода в организациях генерируется или ускоряется с помощью AI-инструментов. По данным Clutch, 59% разработчиков признают, что используют AI-генерированный код, который не до конца понимают [24].
Миграции — особенно уязвимый тип кода в этом контексте по нескольким причинам:
- AI-ассистент не знает объём данных в вашей конкретной таблице на продакшне. Он генерирует синтаксически корректный SQL, но не учитывает, что
ALTER TABLE orders ADD CONSTRAINT ...может заблокировать 180-миллионную таблицу на часы. - Миграции часто генерируются быстро и с минимальным контекстом. В условиях, где на PR смотрят меньше времени, чем нужно, опасные паттерны (
ADD COLUMN NOT NULL DEFAULT, обычныйCREATE INDEX, прямойALTER COLUMN TYPE) проходят code review незамеченными. - Сгенерированный код может содержать placeholder credentials, которые разработчик заменяет реальными и забывает убрать перед коммитом.
Данные подтверждают масштаб проблемы: GitGuardian фиксирует, что AI-assisted commits утекают секреты в 3,2 раза чаще, чем код, написанный вручную [19].
Именно для такого сценария — когда изменение выглядит рабочим, но содержит неочевидный риск — нужен дополнительный слой контроля до merge и до релиза. HighVibe анализирует конкретное изменение в коде, конфигурации и служебных файлах — включая migration-скрипты — и объясняет риск простым языком: что именно может сломаться, почему и какой шаг безопаснее. Не длинный отчёт, а конкретный сигнал: можно ли безопасно принять именно это изменение сейчас.
Ошибки, которые повторяются снова и снова
«На стейджинге работало». Стейджинг с 10 000 строк не репрезентирует продакшн с 80 миллионами. Всегда проверяйте реальный объём данных в целевой таблице.
«У нас есть rollback». Уточните: rollback схемы или rollback данных? Это разные вещи. Если удалили колонку — данные из неё не вернуть без backup [16].
«Миграция маленькая». ADD COLUMN NOT NULL DEFAULT на большой таблице в старых версиях PostgreSQL вызывала полную перезапись. В новых версиях это безопаснее, но ADD CONSTRAINT CHECK без NOT VALID по-прежнему сканирует все строки.
«Мы успеем ночью». Maintenance window снижает пользовательский impact, но не меняет профиль риска миграции. Ночью те же самые блокировки, те же самые объёмы данных — плюс более медленная реакция команды.
«AI написал, значит проверил». AI не знает, сколько строк в вашей таблице, не видит конфигурацию lock_timeout и не знает, что ваш CI-стейджинг в 100 раз меньше продакшна. Проверка по-прежнему за людьми.
Заключение: миграция — это release gate, а не технический долг
Миграция базы данных — не вспомогательный шаг перед настоящим деплоем. Это один из самых рискованных компонентов релиза: она меняет состояние, которое не откатывается так же легко, как код, и блокирует таблицы, от которых зависит весь сервис.
Хорошая новость: большинство инцидентов предотвратимы. Паттерн expand → backfill → contract, lock_timeout, CREATE INDEX CONCURRENTLY, тестирование на реальных объёмах и письменный rollback-план — это не академические рекомендации, а операционная необходимость.
Что делать прямо сейчас:
- Пройти по чек-листу из части 9 для каждой ближайшей миграции.
- Проверить, есть ли в PR-шаблоне явные вопросы про блокировки, rollback и объёмы данных.
- Добавить в CI линтер миграций (например, Squawk для Alembic, strong_migrations для Rails) как обязательный шаг.
- Убедиться, что pre-commit хуки прогоняют secret-сканирование на migration-файлах.
- Завести привычку снимать backup непосредственно перед каждым production-деплоем с миграцией.
Скорость разработки продолжает расти. Контроль изменений — особенно в тех частях кода, которые труднее всего читать и ревьювить — должен расти вместе с ней.
Источники
[1] Medium — "Database Rollbacks in CI/CD: Strategies and Pitfalls" — https://medium.com/@jasminfluri/database-rollbacks-in-ci-cd-strategies-and-pitfalls-f0ffd4d4741a
[2] Medium / Engineering Playbook — "The Database Migration That Locked Production for 4 Hours" — https://medium.com/engineering-playbook/the-database-migration-that-locked-production-for-4-hours-complete-postmortem-1a4955f64b63
[3] Harness — "Introducing AI-Powered Database Migration Authoring" — https://www.harness.io/blog/introducing-ai-powered-database-migration-authoring
[4] Harness — "Database Rollback Strategies in DevOps" — https://www.harness.io/harness-devops-academy/database-rollback-strategies-in-devops
[5] pgroll — "Schema Changes and the Postgres Lock Queue" — https://pgroll.com/blog/schema-changes-and-the-postgres-lock-queue
[6] Handshake Engineering — "PostgreSQL and the Lock Queue" — https://joinhandshake.com/blog/our-team/postgresql-and-lock-queue/
[7] DEV Community — "The 5 PostgreSQL Migration Mistakes That Cause Production Outages" — https://dev.to/mickelsamuel/the-5-postgresql-migration-mistakes-that-cause-production-outages-ngg
[8] postgres.ai — "Zero-downtime Postgres schema migrations need this: lock_timeout and retries" — https://postgres.ai/blog/20210923-zero-downtime-postgres-schema-migrations-lock-timeout-and-retries
[9] datasops.com — "Database Migrations Without Downtime — Expand-Contract, Shadow Tables, and Feature Flags" — https://www.datasops.com/blog/database-migrations-zero-downtime
[10] systemdrd.com — "Database Schema Migrations with Zero Downtime: The Expand-Contract Pattern" — https://systemdr.systemdrd.com/p/database-schema-migrations-with-zero
[11] Pete Hodgson — "Expand/Contract: making a breaking change without a big bang" — https://blog.thepete.net/blog/2023/12/05/expand/contract-making-a-breaking-change-without-a-big-bang/
[12] Xata — "Schema changes and the power of expand-contract with pgroll" — https://xata.io/blog/pgroll-expand-contract
[13] Modus Create — "Microservices Databases Migrations" — https://moduscreate.com/blog/microservices-databases-migrations/
[14] DeployHQ — "Database Migration Strategies for Zero-Downtime Deployments" — https://www.deployhq.com/blog/database-migration-strategies-for-zero-downtime-deployments-a-step-by-step-guide
[15] Codefresh — "Database Migrations in the Era of Kubernetes Microservices" — https://codefresh.io/blog/database-migrations-in-the-era-of-kubernetes-microservices/
[16] SchemaSmith — "Database Rollback Strategies: Migration Recovery Guide" — https://schemasmith.com/guides/database-rollback-strategies.html
[17] AWS — "Migration Rollback Strategies: When Your Migration Doesn't Go as Planned" — https://aws.amazon.com/blogs/migration-and-modernization/migration-rollback-strategies-when-your-migration-doesnt-go-as-planned/
[18] Bytebase — "How to Build a CI/CD Pipeline for Database Schema Migration" — https://www.bytebase.com/blog/how-to-build-cicd-pipeline-for-database-schema-migration/
[19] Snyk — "Why 28 million credentials leaked on GitHub in 2025, and what to do about it" — https://snyk.io/articles/state-of-secrets/
[20] OWASP — "NHI2:2025 Secret Leakage" — https://owasp.org/www-project-non-human-identities-top-10/2025/2-secret-leakage/
[21] Mactores — "Compliance and Security Considerations for a Database Migration" — https://mactores.com/blog/compliance-and-security-considerations-for-a-database-migration
[22] JetBrains Blog — "Database Migrations in the Real World" — https://blog.jetbrains.com/idea/2025/02/database-migrations-in-the-real-world/
[23] Bytebase — "Flyway vs. Liquibase: The Definitive Comparison in 2026" — https://www.bytebase.com/blog/flyway-vs-liquibase/
[24] Clutch — "Blind Trust in AI: Most Devs Use AI-Generated Code They Don't Understand" — https://clutch.co/resources/devs-use-ai-generated-code-they-dont-understand