Прямой ответ
Миграция данных — перенос значений из одной структуры в другую. В отличие от изменения схемы, которое выполняется одной командой, перенос данных обрабатывает существующие строки и занимает время, пропорциональное объёму.
Порядок работ состоит из пяти этапов:
1. Подготовка: новая структура создана, код читает и пишет в обе.
2. Перенос: существующие данные копируются в новую структуру.
3. Проверка: сверка количества и содержимого.
4. Переключение: код читает только из новой структуры.
5. Завершение: старая структура удаляется.
Ключевое правило — старая структура остаётся доступной до завершения проверки. Удаление до сверки делает откат невозможным, а ошибка в преобразовании обнаруживается по обращению пользователя.
Перенос выполняется пакетами, а не одной командой:
BATCH = 5000
last_id = 0
while True:
rows = fetch_batch(source, after_id=last_id, limit=BATCH)
if not rows:
break
write_batch(target, [transform(r) for r in rows])
last_id = rows[-1].id
save_checkpoint(last_id)
Сохранение контрольной точки позволяет продолжить после сбоя, а не начинать перенос заново; о механизме — в статье про отслеживание прогресса.
Три требования к функции преобразования.
1. Детерминированность. Одинаковая исходная строка даёт одинаковый результат при повторном вызове. Иначе повторный прогон даёт другой результат, и сверка не работает.
2. Идемпотентность записи. Повторная обработка строки не создаёт дубликат. Запись выполняется через вставку с обновлением при конфликте либо по первичному ключу.
3. Обработка недопустимых значений. Исходные данные содержат значения, не подходящие под новые ограничения. Преобразование определяет поведение явно: пропуск, значение по умолчанию либо перенос в список исключений.
Отдельный вопрос — запись во время переноса. Данные продолжают изменяться, поэтому перенос существующих строк дополняется передачей изменений; о механизме — в статье про двойную запись.
Изменение схемы под данные выполняется по схеме expand-contract: сначала добавляется новая структура, затем переносятся данные, затем удаляется старая; о подходе — в статье про expand-contract.
Практика: проведение миграции данных
1. Оцените объём и время. Перенос миллиона строк и миллиарда строк — разные задачи по подходу и длительности; о планировании — в статье про оценку нагрузки.
2. Выполняйте перенос пакетами. Одна транзакция на весь объём удерживает блокировки и прерывается по таймауту.
3. Сохраняйте контрольные точки. Продолжение после сбоя с последней позиции экономит часы повторного переноса.
4. Проверяйте количество и содержимое. Совпадение числа строк не означает корректность преобразования; о методике — в статье про сверку.
5. Ведите список исключений. Строки, которые не удалось преобразовать, фиксируются отдельно, а не пропускаются молча.
6. Троттлите нагрузку на базу. Перенос создаёт дополнительную нагрузку, которая влияет на работающий сервис; о блокировках — в статье про deadlock-и.
7. Зафиксируйте точку невозврата. Момент, после которого откат невозможен, определяется заранее и фиксируется в плане.
8. Подготовьте резервную копию до начала. Откат к прежнему состоянию требует точки восстановления; о стратегиях — в статье про резервное копирование.
Рекомендации:
- Не совмещайте миграцию данных с релизом функциональности. Отдельное изменение проще откатить и разобрать при проблеме.
- Репетируйте на копии продуктивных данных. Объём и распределение значений влияют на время и на число исключений.
- Измеряйте скорость переноса. Прогноз завершения нужен для планирования переключения.
- Проверяйте идемпотентность повторным прогоном. Повторный запуск не должен изменять результат.
- Документируйте порядок отката. Процедура возврата описывается до начала переноса, а не в момент проблемы.
Практический сценарий: перенос 40 миллионов строк с изменением структуры
Контекст: поле адреса в виде одной строки требовалось разделить на структурированные компоненты. Таблица содержала 40 миллионов строк и использовалась работающим сервисом.
- Диагностика. Прямое обновление всей таблицы оценивалось в несколько часов с удержанием блокировок. Около двух процентов значений не поддавались разбору по ожидаемому формату.
- Причина. Изменение структуры планировалось одной командой без учёта объёма и некорректных значений. План отката отсутствовал.
- Действия. (а) Добавили новые колонки, оставив прежнюю доступной; (б) перенос выполнили пакетами по пять тысяч строк с сохранением контрольной точки; (в) двойную запись включили до начала переноса, чтобы новые изменения попадали в обе структуры; (г) неразобранные значения собрали в отдельную таблицу и обработали вручную; (д) после сверки количества и выборочной проверки содержимого чтение перевели на новые колонки; (е) прежнюю колонку удалили через две недели после переключения.
- Результат. Перенос занял девять часов без влияния на работу сервиса, а откат оставался возможным до момента удаления прежней колонки.
Вывод: миграция данных — процесс из нескольких этапов, а не одна команда. Пакетная обработка с контрольными точками, двойная запись на время переноса и сверка результата делают перенос безопасным, а сохранение старой структуры до завершения проверки обеспечивает возможность отката.
FAQ
Сколько длится миграция данных? Пропорционально объёму и скорости записи. Оценка выполняется на копии продуктивных данных, а не по числу строк.
Можно ли перенести данные одной командой? Технически да для небольших таблиц. Для миллионов строк одна транзакция удерживает блокировки и прерывается по таймауту.
Что делать со строками, которые не преобразуются? Фиксировать в списке исключений и обрабатывать отдельно. Молчаливый пропуск приводит к потере данных.
Когда удалять старую структуру? После переключения чтения и подтверждения корректности. Удаление до проверки делает откат невозможным.
Источники: PostgreSQL docs (COPY), Alembic docs, Martin Fowler (Parallel Change) — ссылки в шапке.