rsync отчитался об успехе, а на приёмнике другой файл
Лог чист, rsync вернул код 0, счётчик показал переданные байты — а через неделю выясняется, что файл на приёмнике не открывается или содержит не те данные, что источник. Первая реакция — «rsync врёт» или «rsync сломался», но чаще происходит нечто более скучное и куда более объяснимое: rsync честно отчитался о том, что он проверял, а проверял он не то, что вы думали. Разберём, откуда берётся этот разрыв между «успешно» и «совпадает», и как закрыть его проверкой, которая не зависит от доверия к коду возврата одной утилиты.
Содержание
Что на самом деле подтверждает код возврата 0
rsync — это протокол сравнения и переноса байтов между двумя деревьями файлов, и его exit code отвечает на очень конкретный набор вопросов: смог ли процесс подключиться к источнику и приёмнику, хватило ли места на диске, не оборвалось ли соединение, не отсутствовали ли права на чтение или запись, отработали ли системные вызовы read(), write() и close() без ошибок. Код 0 означает, что на этом уровне всё прошло штатно. Ненулевые коды у rsync тоже довольно узко специализированы: 23 — «partial transfer due to error» (часть файлов не скопировалась из-за отдельных ошибок, например прав доступа), 24 — «partial transfer due to vanished source files» (файл исчез в источнике уже после того, как rsync составил список на копирование), 30 — таймаут при передаче данных. Ни один из этих кодов не говорит о содержимом файла на приёмнике — они про процесс переноса, а не про результат сравнения байт в байт.
Это тот же зазор, что и с бэкапами в целом: успешное завершение процесса резервного копирования гарантирует запуск и запись, но не логическую корректность результата — этот механизм подробно разобран в статье про миф «бэкап без ошибок значит рабочий». У rsync ровно та же природа проблемы, просто на уровне отдельного файла, а не целого архива: успех операции копирования — не то же самое, что тождество результата источнику.
Дальше стоит разделить две принципиально разные ситуации, которые снаружи выглядят одинаково — «файл на приёмнике не такой, как в источнике»:
- rsync решил, что файл не нужно копировать, и пропустил его — это вопрос эвристики принятия решения;
- rsync скопировал файл, но то, что легло на диск приёмника, отличается от того, что было передано, — это вопрос надёжности записи после передачи.
Обе ветки разбираются ниже отдельно, потому что лечатся они разными средствами.
Эвристика quick check: почему совпадение метаданных — не совпадение содержимого
По умолчанию, без флага -c/--checksum, rsync решает, копировать ли файл, по так называемой quick check-эвристике: если у файла на источнике и приёмнике совпадают размер в байтах и время модификации (mtime), файл считается идентичным и пропускается без чтения содержимого. Это осознанный компромисс в пользу скорости — читать и хешировать содержимое каждого файла на гигабайтных деревьях ради проверки того, что почти всегда и так не менялось, было бы избыточно дорого. Подробно про то, из чего складывается цена операций rsync на больших деревьях, — в статье про потолок rsync на миллионах файлов.
Проблема в том, что размер и mtime — это совпадение метаданных, а не доказательство идентичности содержимого. Эвристика ошибается в конкретных, вполне типовых сценариях:
- Файл на приёмнике уже существовал — например, был восстановлен из другого, более старого бэкапа, скопирован вручную или создан другим процессом — и по случайному совпадению имеет тот же размер и близкое mtime. rsync посчитает его актуальным и не тронет, хотя содержимое другое.
- Разная точность хранения времени модификации. Некоторые файловые системы (классический пример — FAT32) хранят mtime с точностью до 2 секунд, а не до наносекунды. Если исходный файл менялся дважды в пределах этого окна, на приёмнике может «случайно» оказаться mtime, который quick check сочтёт совпадающим.
- Рассинхронизация часов между хостами или сдвиг часового пояса при монтировании сетевых ФС (NFS, SMB) — метаданные mtime, которые видит rsync, зависят от системного времени той стороны, которая их проставляет, и если оно «врёт», эвристика получает искажённые входные данные.
- Восстановление после сбоя. Если процесс на приёмнике был прерван (обрыв сети, OOM-killer, перезагрузка) уже после того, как файл частично записан и его метаданные обновлены, а повторный запуск rsync по какой-то причине не форсирует полную перезапись — итоговое состояние может «пройти» quick check, оставаясь по факту повреждённым.
Отдельно стоит грабля с флагом --append (не путать с более безопасным --append-verify). --append предполагает, что уже существующий на приёмнике префикс файла корректен, и просто дописывает оставшиеся байты источника поверх, не проверяя совпадение уже имеющейся части. Если тот префикс на самом деле битый — например, остался от оборвавшейся предыдущей передачи, — итог будет содержать чужой мусор в начале и правильные байты в хвосте, а rsync честно отрапортует об успехе: он ведь действительно дописал ровно то, что должен был. --append-verify устраняет эту дыру: перед дозаписью он сверяет контрольную сумму уже присутствующего префикса и при расхождении перекачивает файл заново, а не доверяет тому, что уже лежит на диске.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверПорча уже после того, как rsync отчитался об успехе
Вторая ветка — файл действительно был передан целиком и корректно, а разошёлся с источником уже после того, как rsync завершился с кодом 0. Здесь причина не в логике rsync, а в том, что «файл записан» с точки зрения системного вызова write() и «файл физически лежит на носителе в неизменном виде» — не одно и то же событие, и между ними может пройти значительное время.
Типичная цепочка: write() кладёт данные в страничный кэш ядра и возвращает успех немедленно, реальная запись на физический носитель (writeback) происходит асинхронно позже, зависит от политики кэширования, настроек файловой системы и наличия/поведения fsync. Если между этим отложенным сбросом на диск и самим диском что-то пойдёт не так — сбойный сектор, ошибка ремапа на SSD без защиты от потери питания, баг прошивки RAID-контроллера, кэш на запись без резервной батареи при внезапном отключении питания, — на носителе может оказаться не то, что было в буфере в момент, когда rsync закрыл файл и вышел с кодом 0. Утилита физически не может проверить то, что случится с байтами после того, как она передала их операционной системе и получила подтверждение от close() — с этого момента ответственность лежит на слое ФС, кэша и контроллера диска.
Второй источник — постепенная порча уже после успешной и полностью корректной записи, так называемый bit rot: одиночные биты на носителе меняют значение со временем без явной команды записи, из-за деградации магнитного слоя, космического излучения, дефектов флеш-памяти. Для такого рода порчи момент передачи rsync вообще не имеет значения — файл был идеально скопирован, а разошёлся с источником спустя недели или месяцы хранения. Разбор этого механизма и того, почему он остаётся незаметным без специальных проверок, — в статье про тихую порчу файлов. Похожая картина возможна и на уровне диска целиком, когда физически исправное по SMART устройство всё равно портит данные из-за проблемы в другом звене цепочки — например, в кабеле или контроллере.
Практический вывод из этого раздела простой: код возврата rsync — это утверждение про момент передачи, а не гарантия на будущее. Полагаться на него как на постоянное свидетельство целостности файла — ошибка вне зависимости от того, насколько аккуратно настроена сама передача.
Включаем проверку содержимого в момент передачи: --checksum
Первый рубеж защиты — заставить rsync не доверять размеру и mtime, а действительно сравнивать содержимое. За это отвечает флаг -c / --checksum: вместо quick check rsync читает файл целиком на обеих сторонах, считает контрольную сумму содержимого и сравнивает суммы, а не метаданные.
rsync -avc --stats /data/ user@backup-host:/srv/data/
echo "exit code: $?"
У этого подхода есть понятная цена: чтобы посчитать контрольную сумму, нужно прочитать весь файл целиком с обеих сторон ещё до принятия решения о передаче — на больших деревьях это ощутимая дополнительная нагрузка на диск и время выполнения по сравнению с quick check, который довольствуется одним stat(). Отсюда практическое правило: --checksum не обязателен на каждый прогон регулярной синхронизации, но точно оправдан в конкретных ситуациях —
- при первой синхронизации нового приёмника, когда доверия к его текущему состоянию нет;
- после восстановления после сбоя источника или приёмника;
- при переносе данных между файловыми системами с разной точностью mtime (например, со старого раздела FAT/exFAT);
- периодически, отдельным «сверочным» прогоном поверх более лёгкой ежедневной синхронизации без
-c.
Полезная привычка перед боевым запуском с --checksum — сухой прогон с --dry-run и --itemize-changes (-i), чтобы увидеть, что именно rsync собирается сделать, не трогая файлы:
rsync -avci --dry-run /data/ user@backup-host:/srv/data/
Вывод в формате >f.st...... path/to/file расшифровывается по позициям: первый символ — тип операции (> — файл будет передан), второй — тип объекта (f — обычный файл), дальше буквы c, s, t и другие показывают, что именно отличается — содержимое (c, появляется только при --checksum), размер (s), время модификации (t). Если в строке стоит c, значит именно перерасчёт контрольной суммы выявил различие, которое quick check пропустил бы — это прямое подтверждение того, что описанная выше эвристика на этом файле сработала бы неверно.
Независимая проверка после копирования
Даже полностью корректный прогон rsync с --checksum подтверждает состояние только на момент своего завершения. Он ничего не знает о том, что случится с байтами на приёмнике через час или через месяц, и не защищает от порчи уровня диска или кэша, описанной выше. Поэтому вторым, независимым от rsync рубежом должна быть отдельная проверка контрольных сумм — не силами самого rsync, а сторонним инструментом, который читает готовый результат так, как его будет читать реальное приложение.
Практическая схема на связке sha256sum:
# на источнике: считаем сумму по каждому файлу относительно каталога
cd /data
find . -type f -print0 | xargs -0 sha256sum > /tmp/data.sha256
# сама передача
rsync -a --checksum ./ user@backup-host:/srv/data/
# манифест едет вместе с данными
rsync -a /tmp/data.sha256 user@backup-host:/srv/
# на приёмнике: независимая сверка, без участия rsync
ssh user@backup-host 'cd /srv/data && sha256sum -c ../data.sha256' | grep -v ': OK$'
Смысл последней команды — вывести только строки с расхождением (FAILED) или отсутствующим файлом, отфильтровав благополучные OK. Если вывод пуст — содержимое на приёмнике действительно, байт в байт, совпадает с тем, что было на источнике в момент снятия манифеста, и это утверждение уже не зависит от того, как именно рассуждал rsync при принятии решения о копировании.
Здесь есть тонкость: манифест сумм нужно снимать до передачи и хранить его как отдельный, независимый артефакт, а не пересчитывать его же на источнике после факта — иначе вы просто дважды проверите одно и то же состояние источника, не сказав ничего о приёмнике. Ту же логику стоит применять с любым инструментом контроля целостности — рассуждение о том, зачем вообще нужен независимый контроль неизменности файлов, которые «вроде бы никто не трогал», подробно разобрано в статье про контроль целостности файлов.
Для действительно критичных данных сверку стоит повторять не один раз сразу после копирования, а регулярно — например, отдельным еженедельным заданием cron, которое пересчитывает суммы на приёмнике и сравнивает их с последним сохранённым манифестом источника. Это единственный способ поймать порчу, которая случилась не во время передачи, а спустя время хранения — тот самый bit rot, для которого момент, когда rsync давно закрыл сессию и вышел с кодом 0, никак не является гарантией на будущее.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверНужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.
Частые вопросы
Если rsync вернул код 0, значит ли это, что все файлы точно скопировались?
Нет. Код 0 означает, что процесс передачи не встретил ошибок уровня сети, прав доступа или места на диске. Он не проверяет содержимое файлов на приёмнике байт в байт, если не был явно указан флаг --checksum, и ничего не гарантирует про состояние файла после завершения rsync.
Почему rsync не использует --checksum по умолчанию, если это надёжнее?
Потому что это ощутимо дороже: чтение и хеширование содержимого каждого файла на обеих сторонах перед принятием решения о передаче резко увеличивает время и нагрузку на диск, особенно на больших деревьях. Quick check по размеру и mtime — сознательный компромисс в пользу скорости, который в подавляющем большинстве повседневных синхронизаций работает корректно.
Достаточно ли одного --checksum, чтобы гарантировать целостность данных?
Он закрывает только момент решения о передаче — то есть подтверждает, что переданный контент совпал с источником на момент сравнения. Он не защищает от порчи, которая произойдёт на приёмнике позже: при сбое записи на диск, из-за bit rot или аппаратной проблемы. Для этого нужна отдельная независимая проверка после копирования.
Чем sha256sum лучше встроенной проверки rsync?
Тем, что она независима: манифест сумм снимается один раз на источнике, хранится отдельно и сверяется на приёмнике сторонним вызовом, без участия логики самого rsync. Это ловит и ошибки в самом rsync или его настройке, и порчу, случившуюся после того, как rsync уже завершил работу.
Как быть с --append при докачке больших файлов?
Использовать --append-verify вместо простого --append. Обычный --append доверяет уже существующему на приёмнике префиксу файла и просто дописывает остаток без проверки — если префикс был битым (например, после оборванной предыдущей передачи), итоговый файл окажется повреждён, а rsync всё равно отчитается об успехе. --append-verify сверяет контрольную сумму уже имеющейся части перед дозаписью.
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →