Почему миграция данных отличается от развертывания приложений

У вас отлично настроен CI/CD пайплайн. Развертывание приложений — рутина. Rolling updates, blue-green deployments, canary releases — ваша команда справляется с ними без проблем. И тут кто-то говорит: «Нам нужно изменить схему базы данных и перенести данные из старой таблицы в новую». Внезапно в комнате наступает тишина. Люди начинают задавать вопросы. Кто-то проверяет, свежие ли бекапы. Кто-то спрашивает, можно ли это сделать в часы обслуживания. Уверенность, которая была мгновение назад, исчезает.

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

Проблема с состоянием

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

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

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

Следующая диаграмма сравнивает два процесса бок о бок:

flowchart TD subgraph AppDeploy["Развертывание приложения"] A1[Код] --> A2[Сборка] A2 --> A3[Тестирование] A3 --> A4[Деплой] A4 --> A5{Успешно?} A5 -- Да --> A6[Готово] A5 -- Нет --> A7[Откат к старой версии] A7 --> A6 end subgraph DataMigrate["Миграция данных"] B1[Изменение схемы] --> B2[Перенос данных] B2 --> B3{Успешно?} B3 -- Да --> B4[Проверка и сверка] B3 -- Нет --> B5[Восстановление из бекапа] B5 --> B6[Возможна потеря данных] end AppDeploy -.->|Обратимый процесс| DataMigrate

Прямое влияние на пользователей

Когда приложение падает, пользователи видят страницу с ошибкой. Они могут обновить страницу, попробовать позже или обратиться в поддержку. Это неприятно, но их данные в безопасности.

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

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

Длительность и ограничения

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

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

Такая длительная работа порождает проблемы координации. Кто отслеживает миграцию? Что делать, если она упадет на полпути? Как сообщить о статусе остальной команде? Обычно такие вопросы не задают при деплое кода.

Что делает миграцию данных безопасной

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

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

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

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

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

Практический чек-лист перед любой миграцией данных

Перед тем как запускать миграцию данных в продакшене, пройдитесь по этому списку:

  • Идемпотентен ли скрипт миграции? Можно ли его запустить дважды без проблем?
  • Выполнили ли вы dry-run на копии продакшен-данных?
  • Знаете ли вы, сколько времени займет миграция? Заложили ли вы это окно?
  • Есть ли план отката, который не сводится к «просто восстановимся из бекапа»?
  • Написали ли вы запросы для сверки, чтобы проверить результат?
  • Знает ли команда, кто отслеживает миграцию и кому звонить, если что-то пойдет не так?

Вывод

Миграция данных — это не развертывание приложения с другим скриптом. Это другая категория работы, которая требует своего процесса, своих защитных мер и своего определения «готово». В следующий раз, когда ваша команда запланирует изменение схемы или перенос данных, остановитесь и спросите: покрыты ли у нас идемпотентность, dry-run, бэкфилл и сверка? Если ответ «нет» — вы не готовы запускать эту миграцию.