Запуск миграций базы данных в продакшене без потери сна

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

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

Проблема не только техническая. Это ещё и вопрос тайминга, координации и умения вовремя остановиться.

Когда запускать миграцию

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

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

Проблема блокировок

Самый частый источник проблем при продакшен-миграциях — блокировки. Когда вы выполняете команду вроде ALTER TABLE, большинство БД блокируют таблицу, чтобы предотвратить другие изменения на время операции. Если эта команда выполняется долго, каждый запрос от приложения, обращающийся к этой таблице, либо ждёт, либо падает. Пользователи начинают видеть медленные страницы или сообщения об ошибках.

Некоторые базы данных предлагают способы уменьшить блокировки. PostgreSQL поддерживает CREATE INDEX CONCURRENTLY, который строит индекс без блокировки записи. В MySQL есть аналогичные опции для некоторых операций. Но не каждое изменение можно сделать без блокировки. Добавление колонки со значением по умолчанию, изменение типа колонки или удаление колонки часто требуют эксклюзивной блокировки.

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

Например, вот безопасный паттерн добавления колонки со значением по умолчанию в PostgreSQL, позволяющий избежать длительной эксклюзивной блокировки:

-- Шаг 1: Добавляем колонку как nullable (быстро, без перезаписи данных)
ALTER TABLE users ADD COLUMN display_name text;

-- Шаг 2: Заполняем колонку маленькими батчами
UPDATE users SET display_name = username WHERE display_name IS NULL LIMIT 1000;
-- Повторять, пока не останется строк

-- Шаг 3: Устанавливаем NOT NULL (быстро, без перезаписи данных)
ALTER TABLE users ALTER COLUMN display_name SET NOT NULL;

Разбивайте большие миграции на маленькие шаги

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

Более безопасный подход — разбить работу на несколько маленьких миграций:

  1. Добавить новую колонку без значения по умолчанию.
  2. Запустить фоновую задачу для заполнения новой колонки батчами.
  3. Проверить, что данные полны и корректны.
  4. Удалить старую колонку отдельной миграцией.

Каждый шаг можно проверить перед переходом к следующему. Если что-то пошло не так на втором шаге, вы ничего не потеряли. Можно исправить данные и повторить. Такой подход занимает больше времени, но он гораздо менее рискованный.

Встройте проверки безопасности в пайплайн

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

Следующая блок-схема иллюстрирует описанный выше процесс проверки безопасности:

flowchart TD A[Start Migration] --> B{Traffic Low?} B -- Yes --> C[Run Migration with Locking Analysis] B -- No --> D[Wait and Recheck] D --> B C --> E{Locks Released?} E -- Yes --> F{Query Latency Normal?} E -- No --> G[Stop and Notify] F -- Yes --> H[Confirm Success] F -- No --> G G --> I[Rollback if Needed]

Во время миграции пайплайн должен мониторить:

  • Длительность выполнения: сколько времени занимает каждый оператор
  • Время ожидания блокировки: ждут ли другие запросы освобождения блокировок
  • Уровень ошибок: любые ошибки от базы данных или приложения

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

Что происходит после завершения миграции

Миграция завершилась без ошибок. Пайплайн зелёный. Но работа ещё не закончена.

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

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

Практический чек-лист для продакшен-миграций

Перед запуском миграции в продакшене пройдитесь по этому чек-листу:

  • Протестируйте миграцию на копии продакшен-данных, а не только на базе для разработки
  • Измерьте, сколько времени занимает каждый оператор и какие блокировки он захватывает
  • Проверьте, поддерживает ли версия БД альтернативы без блокировок для вашей операции
  • Проверьте наличие долгих запросов перед запуском миграции
  • Настройте мониторинг времени ожидания блокировок и уровня ошибок во время миграции
  • Определите чёткое условие прерывания: какая метрика или ошибка вызывает остановку
  • Подготовьте план отката: как отменить миграцию, если что-то пошло не так
  • Уведомите команду до и после запуска миграции
  • Запланируйте перезапуск приложения или обновление соединений после миграции

Вывод

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