Материал

Миграции базы данных: что проверить перед выпуском

Чек-лист, который стоит пройти до нажатия кнопки 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].

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

  1. Постоянное состояние. База хранит данные между деплоями, между версиями кода, между инцидентами. Миграция меняет это состояние — часто необратимо.
  2. Временной разрыв. В системах с нулевым даунтаймом старый и новый код некоторое время работают одновременно с одной схемой. Если схема несовместима с предыдущей версией кода, старые инстансы начинают падать ещё во время деплоя.
  3. Блокировки. 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]:

  1. CREATE INDEX без CONCURRENTLY — захватывает SHARE LOCK на всё время построения индекса, блокируя все write-операции.
  2. ALTER TABLE ADD COLUMN ... NOT NULL DEFAULT <значение> — до PostgreSQL 11 это вызывало полную перезапись таблицы; в новых версиях стало безопаснее, но только если значение по умолчанию не volatile.
  3. ALTER COLUMN TYPE — если новый тип требует другого бинарного представления, PostgreSQL перезаписывает каждую строку; на большой таблице это часы.
  4. ADD CONSTRAINT без NOT VALID — PostgreSQL сканирует все строки для проверки; безопасная альтернатива — добавить с NOT VALID, затем VALIDATE CONSTRAINT в фоне.
  5. Отсутствие 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]:

  1. Деплой миграции (только backward-compatible изменения — добавление колонок, индексов, таблиц).
  2. Деплой нового кода (теперь он пишет в обе структуры).
  3. Верификация: проверка, что новый код работает корректно.
  4. Отдельный деплой cleanup-миграции (удаление старой структуры).

Для Kubernetes-окружений отдельно стоит проверить, что миграция не запускается при старте каждого пода параллельно. Нужна защита от параллельного запуска (advisory lock или механизм миграционного инструмента) [15].

Часть 5. Rollback: что на самом деле можно откатить

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

Специалисты SchemaSmith формулируют это прямо: если миграция слила две колонки в одну, удалила таблицу после переноса данных в другую или изменила тип с потерей точности — ни один инструмент не откатит это автоматически без backup исходных данных [16].

Перед каждой production-миграцией нужно письменно ответить на шесть вопросов [16]:

  1. Что изменяет эта миграция: только схему, только данные или и то, и другое?
  2. Если откат нужен — какие данные, записанные после миграции, будут потеряны?
  3. Сколько времени займёт откат: секунды (revert скрипт), минуты (down-migration) или часы (полное восстановление из backup)?
  4. Есть ли актуальный backup, снятый непосредственно перед запуском миграции?
  5. Кто принимает решение об откате и кто технически его исполняет?
  6. Проверен ли 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-деплоем, содержащим миграцию.

Корректность и безопасность скрипта:

  1. Миграция проверена на наличие секретов и credentials.
  2. Права, под которыми запускается миграция, соответствуют принципу минимальных привилегий.
  3. В коде нет PII или sensitive data в явном виде.
  4. Migration-файл не изменялся после первого применения на любой среде.

Обратная совместимость:

  1. Миграция совместима со старой версией application code — если деплой rolling, оба кода будут работать одновременно.
  2. Ни одна колонка или таблица, используемая текущей версией кода, не удаляется и не переименовывается в этом деплое.
  3. Ломающие изменения декомпозированы по паттерну expand → backfill → contract на несколько отдельных деплоев.

Блокировки и производительность:

  1. Для всех новых индексов используется CREATE INDEX CONCURRENTLY (PostgreSQL) или эквивалент.
  2. lock_timeout установлен для DDL-сессии.
  3. NOT NULL-ограничения добавляются через NOT VALID → VALIDATE CONSTRAINT.
  4. Время выполнения миграции проверено на данных, сопоставимых по объёму с продакшном.
  5. Учтён replication lag при наличии read-реплик.

Rollback:

  1. Определён план отката: что делать, если миграция упала на полпути.
  2. Rollback-скрипт написан и проверен на стейджинге.
  3. Актуальный backup снят непосредственно перед запуском production-деплоя.
  4. Определено время исполнения отката и лицо, принимающее решение.

CI/CD и процесс:

  1. Миграция прошла через code review с явной проверкой на опасные паттерны.
  2. На стейджинге миграция применялась чисто, без ошибок.
  3. Нет параллельного запуска миграции на нескольких нодах (advisory lock или аналог).
  4. 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-план — это не академические рекомендации, а операционная необходимость.

Что делать прямо сейчас:

  1. Пройти по чек-листу из части 9 для каждой ближайшей миграции.
  2. Проверить, есть ли в PR-шаблоне явные вопросы про блокировки, rollback и объёмы данных.
  3. Добавить в CI линтер миграций (например, Squawk для Alembic, strong_migrations для Rails) как обязательный шаг.
  4. Убедиться, что pre-commit хуки прогоняют secret-сканирование на migration-файлах.
  5. Завести привычку снимать 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

Нужен такой же контроль в ваших изменениях и релизах?

Покажем пример такого замечания на вашем сценарии и соберём пилотный контур без лишней суеты.

Запросить пилот Все статьи
Дальше по теме

Ещё материалы

Все статьи
24 июня 2026 18 мин

Политика использования ИИ в команде разработки: как не опоздать, когда всё уже идёт

Если разработчики уже пишут код с ИИ ассистентами, политика нужна сегодня — а не после первого инцидента TL;DR Более 84% разработчиков уже используют или планируют использовать ИИ инструменты 1 , и большинство делают эт…

Открыть материал
23 июня 2026 16 мин

Регистрация и онбординг пользователей: где появляются первые уязвимости

Форма регистрации выглядит просто. За ней скрывается один из наиболее атакуемых периметров приложения. TL;DR Форма регистрации и онбординг флоу — это первая поверхность атаки, которую видит злоумышленник. Большинство уя…

Открыть материал
23 июня 2026 15 мин

Бизнес-логика и мошенничество в продукте: что проверять до запуска

Ваш продукт работает правильно. Но это не значит, что он работает безопасно. TL;DR Уязвимости бизнес логики — это не сломанный код, а сломанные правила: приложение делает именно то, что написано, но не то, что задумывал…

Открыть материал