Прямой ответ
Миграция файлов между хранилищами выполняется по схеме «копирование существующего + двойная запись нового + сверка + переключение». Попытка сделать это одним копированием с остановкой записи почти всегда приводит либо к длительному простою, либо к потере данных, записанных во время переноса.
Порядок работ:
| Этап | Действие | Риск, который закрывает |
|---|---|---|
| 1. Двойная запись | Новые файлы пишутся в оба хранилища | Расхождение по новым данным |
| 2. Backfill | Копирование исторических объектов | Потеря старых данных |
| 3. Повторная сверка | Копирование изменённого за время backfill | Изменения во время переноса |
| 4. Проверка | Сравнение списка и контрольных сумм | Тихое расхождение |
| 5. Переключение чтения | Чтение из нового хранилища | Возможность отката |
| 6. Отключение записи в старое | Прекращение двойной записи | Завершение миграции |
Ключевой принцип: чтение переключается последним и обратимо. Пока старое хранилище содержит актуальные данные, откат сводится к возврату конфигурации.
Копирование выполняют специализированными инструментами:
# rclone: синхронизация с проверкой по размеру и контрольной сумме
rclone sync s3old:docs s3new:docs \
--checksum --transfers 16 --checkers 32 \
--progress --log-file migrate.log --log-level INFO
Флаг --checksum сравнивает хеши, а не только размер и дату, поэтому тихое повреждение при передаче обнаруживается. Для больших объёмов используют управляемые сервисы репликации, которые выполняют перенос инкрементально.
Сверка результата — отдельный шаг, а не часть копирования:
def compare(src, dst, prefix):
a = {o["Key"]: (o["Size"], o["ETag"]) for o in list_all(src, prefix)}
b = {o["Key"]: (o["Size"], o["ETag"]) for o in list_all(dst, prefix)}
missing = set(a) - set(b)
diff = {k for k in set(a) & set(b) if a[k] != b[k]}
return {"missing": len(missing), "differ": len(diff)}
Важная деталь: ETag объекта, загруженного частями, не равен контрольной сумме файла. При сравнении разноразмерных загрузок нужно использовать явный хеш из метаданных или сверять содержимое — о хешах подробнее в статье про дедупликацию.
Практика: миграция без потерь
1. Оцените объём и время заранее. Скорость копирования ограничена полосой и числом запросов в секунду. Миллион мелких файлов переносится дольше, чем терабайт в ста крупных.
2. Ограничьте нагрузку на исходное хранилище. Параллельное копирование без ограничения замедляет боевые запросы. Число потоков подбирают по фактической задержке чтения.
3. Сделайте перенос возобновляемым. Прерывание на половине объёма не должно требовать начала заново. Инструменты с инкрементальной синхронизацией пропускают уже скопированное — подход тот же, что в ETL-конвейерах.
4. Логируйте каждую операцию. При расхождении нужен список проблемных ключей, а не общее число.
5. Перенесите метаданные. Пользовательские метки, Content-Type и права доступа не копируются автоматически во всех режимах.
6. Проверьте ссылки на файлы. Если приложение хранит абсолютные URL, их нужно обновить или оставить обратный прокси на старое хранилище.
7. Оставьте старое хранилище на время. Удаление сразу после переключения лишает возможности отката; срок выдержки задают исходя из того, за какое время обнаруживаются проблемы.
Рекомендации:
- Начинайте с пилота на подмножестве. Перенос одного префикса выявляет проблемы с именами, кодировками и метаданными до основного прогона.
- Не удаляйте источник до завершения проверки. Операция удаления должна быть отдельным решением, а не частью скрипта копирования.
- Учитывайте версионирование. Если в источнике включено версионирование, решите, переносятся ли старые версии; обычно достаточно текущих.
- Планируйте окно для финальной сверки. Между последним копированием и переключением записи быть не должно, иначе появляется расхождение.
- Измеряйте результат. Число перенесённых объектов, объём и время должны совпадать с исходной статистикой — о работе с S3 в статье про MinIO.
Практический сценарий: после миграции часть изображений отдавалась с ошибкой
Контекст: файлы перенесли из одного бакета в другой, чтение переключили. Через день выяснилось, что около 3 % изображений не открываются.
- Диагностика. Сверка показала совпадение числа объектов, но расхождение по
Content-Type: у части файлов в новом хранилище стоялapplication/octet-stream. - Причина. Копирование выполнялось в режиме, который не переносил метаданные, а тип определялся по расширению. Файлы с расширением без точки и с символами в верхнем регистре не определились.
- Дополнительная находка. Проверка по ETag дала 12 расхождений для объектов, загруженных частями: ETag различался из-за разного размера частей, хотя содержимое совпадало.
- Действия. (а) Повторно скопировали объекты с переносом метаданных и явной установкой
Content-Typeпо сохранённой в базе таблице; (б) заменили сверку по ETag на сверку по SHA-256 из метаданных объекта; (в) добавили проверку читаемости выборки в 1 000 файлов после миграции; (г) вернули чтение на старое хранилище на время исправления, поскольку оно ещё не было очищено; (д) включили перенос метаданных в чек-лист миграции. - Результат. Все файлы отдаются с корректным типом; расхождений по контрольным суммам нет, а проверка выборки входит в стандартную процедуру.
Вывод: совпадение числа объектов не означает успеха миграции. Обязательны сверка контрольных сумм, перенос метаданных и проверка фактической читаемости выборки.
FAQ
Можно ли мигрировать без двойной записи? Да, если допустимо окно остановки записи. Для небольших объёмов это проще, но требует согласованного простоя.
Что быстрее: копирование на стороне провайдера или через приложение? На стороне провайдера: данные не проходят через приложение, поэтому не расходуют его полосу и память. Через приложение копирование нужно только при преобразовании данных.
Как мигрировать, если файлы хранятся на локальном диске? Сначала перенести в объектное хранилище, затем мигрировать между бакетами. Прямая загрузка с диска выполняется с контролем параллелизма и повторами.
Нужно ли обновлять ссылки в базе после миграции? Если хранятся относительные ключи — нет. Абсолютные URL требуют обновления либо сохранения обратного прокси на прежнее расположение.
Источники: rclone docs, AWS DataSync, S3 (проверка целостности) — ссылки в шапке.