Коротко. Перенос без остановки это начальная копия, догоняющее зеркалирование и переключение. У mc mirror без ключа --overwrite изменённые объекты остаются старыми, а mc diff при расхождениях возвращает 0. Если сервисы RX ходят в хранилище по имени, которое вы контролируете, переключение обходится без перезапуска.

Хранилище документов на S3 приходится переносить: меняется провайдер, свой MinIO уезжает на новое железо или в другую площадку, кластер пересобирают с другим числом дисков. Документов терабайты, копирование идёт сутками, а остановить систему на сутки никто не даст. Разбираем, как переносить при работающей системе и где при этом тихо теряются изменения. Обе ловушки из статьи мы получили на проверке переноса между двумя MinIO в контейнерах.

Переход с файлового хранилища на объектное это другой сценарий: для него у вендора есть утилита S3Tool, она описана в справке RX. Здесь речь о переносе из одного S3 в другое.

Симптом

Перенос делают «как обычно»: ставят на ночь копирование, утром переключают систему на новое хранилище. Через неделю пользователь открывает документ и видит версию недельной давности или ошибку «файл не найден». Копирование отработало без ошибок, а часть документов, изменённых или созданных во время копирования, в новом хранилище старая или отсутствует.

Почему так

Копирование одним проходом фиксирует состояние на момент, когда каждый объект был прочитан. Всё, что RX записала после этого, в копию не попало. Чем дольше копирование, тем больше таких объектов.

Поэтому перенос без остановки делается в три фазы:

  1. Начальная копия. Долгая, при работающей системе. Переносит основной объём.
  2. Догоняющее зеркалирование. Переносит то, что изменилось после начальной копии, и продолжает переносить изменения, пока система работает со старым хранилищем.
  3. Переключение. Система начинает писать в новое хранилище, зеркалирование доносит последние изменения и останавливается.

Две ловушки mc mirror

mc mirror из клиента MinIO самый частый инструмент для этой работы. У него две особенности, которые ломают перенос без единого сообщения об ошибке.

Изменённые объекты не обновляются без ключа --overwrite. По умолчанию mc mirror не перезаписывает объект, который уже есть в целевом бакете. Новые объекты копируются, удалённые с ключом --remove удаляются, а изменённые остаются в старом виде. На проверке мы перезаписали объект в исходном бакете при работающем mc mirror --watch: в целевом бакете остался прежний файл размером 110 КБ вместо нового 7,6 КБ. С ключом --overwrite и прошлое расхождение, и новые перезаписи переносятся.

Для RX это важно: содержимое версии документа обычно не перезаписывается, но служебные объекты и повторные загрузки бывают. Проверять, какие операции у вас реально случаются, дороже, чем всегда ставить --overwrite.

mc diff находит расхождение и завершается с кодом 0. Сверка mc diff печатает строки с отличающимися объектами, но код возврата у неё нулевой и при найденных расхождениях. Скрипт вида mc diff ... && echo "всё совпало" скажет, что всё совпало. Проверять нужно, что вывод пустой.

Ещё одно: mc mirror --watch получает изменения через уведомления, которые отдаёт MinIO. Если исходное хранилище у облачного провайдера, на это рассчитывать нельзя. Там догоняющую фазу делают повторными проходами без --watch, каждый следующий проход короче предыдущего.

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

Имена хранилищ в командах: old и new, бакет rx-docs. Адреса и ключи задаются переменными окружения клиента или командой mc alias set.

Фаза 1. Начальная копия, при работающей системе:

mc mirror --preserve old/rx-docs new/rx-docs

Фаза 2. Догоняющее зеркалирование. Если источник MinIO, запускаем постоянное зеркалирование:

mc mirror --watch --overwrite --remove --preserve old/rx-docs new/rx-docs

Если источник у провайдера, повторяем проходы, пока разница не станет маленькой:

mc mirror --overwrite --remove --preserve old/rx-docs new/rx-docs

Того же можно добиться через rclone: rclone sync old:rx-docs new:rx-docs --checksum. Команда sync приводит целевой бакет в точное соответствие с исходным, включая удаления.

Сверка перед переключением:

mc diff old/rx-docs new/rx-docs | tee diff.txt
test ! -s diff.txt && echo "расхождений нет"
mc ls --recursive old/rx-docs | wc -l
mc ls --recursive new/rx-docs | wc -l

Пустой вывод diff и одинаковое число объектов. Плюс выборочно открыть несколько документов через систему после переключения.

Фаза 3. Переключение. Здесь два варианта, и от выбора зависит, будет ли простой вообще.

  • Через имя. Если сервис хранилищ RX обращается к S3 по имени, которое вы контролируете (запись DNS или адрес на своём прокси), переключение это смена записи или адреса за прокси. Ключи доступа и имя бакета на новом хранилище должны совпадать со старыми. Сервисы RX не перезапускаются.
  • Через настройки RX. Если меняется адрес, ключи или имя бакета, их меняют в параметрах сервиса хранилищ в config.yml и перезапускают сервис. Простой равен времени перезапуска сервиса, минуты, а не сутки.

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

Что ещё проверить

  • Версионирование бакета. Если в старом бакете оно включено, mc mirror переносит только текущие версии объектов. Если старые версии нужны, их переносят отдельно или заранее решают, что они не нужны.
  • Правила жизненного цикла и политики доступа. Они не переносятся вместе с объектами, их настраивают на новом бакете отдельно.
  • Место на новом хранилище. С учётом коэффициента избыточности, если это кластер с кодами стирания: полезный объём там меньше суммы дисков.
  • Бэкап. Схему бэкапа хранилища перенастраивают на новый бакет в тот же день, а не «потом».

Чего не делать

  • Не переключаться после одного прохода копирования. Всё, что изменилось за время копирования, потеряется.
  • Не запускать mc mirror без --overwrite. Изменённые объекты останутся старыми.
  • Не проверять сверку по коду возврата mc diff. Только по пустому выводу.
  • Не удалять старый бакет сразу. Пусть постоит, пока не пройдёт хотя бы один полный цикл бэкапа нового.