Привет, Хабр! В первой части я разобрал основы rsync: синтаксис, ключевые опции и работу по SSH. Эти аспекты позволяют эффективно пользоваться утилитой на базовом уровне. В этой статье заглянем «под капот» и научимся тонко контролировать весь процесс синхронизации и диагностировать проблемы с производительностью...

Глава 1: исключения и фильтры — тотальный контроль над синхронизацией

1.1. Базовые исключения: --exclude и --exclude-from

Иногда возникает необходимость скопировать не всё подряд, а всё кроме некоторых файлов или папок. rsync предоставляет для этого мощный и гибкий механизм исключений.

--exclude=PATTERN: исключение по шаблону

Опция позволяет указать один шаблон (pattern) для файлов или директорий, которые нужно пропустить при синхронизации. Шаблоны похожи на упрощённые регулярные выражения. Рассмотрим их подробнее:

1. Символ *: заменяет любую последовательность любых символов (включая отсутствие символов).

  • *.txt - соответствует всем файлам, которые заканчиваются на .txt (например, file.txtdocument.txt1.txt).

  • project* - соответствует всем файлам, которые начинаются на project (например, projectproject1project_backup).

  • data - соответствует всем файлам, которые содержат в названии слово data (например, datamydatadatabase_backupold_data_file.log).

2. Символ ?: заменяет ровно один любой символ.

  • file?.txt - соответствует файлам file1.txtfileA.txtfile_.txt, но не file10.txt (потому что там два символа вместо одного) и не file.txt (потому что после file должен быть хоть один символ).

3. Символ [ ]: заменяет один символ из тех, что перечислены внутри скобок. Возможно задавать диапазоны.

  • file[123].txt - соответствует только файлам file1.txtfile2.txtfile3.txt.

  • report_[0-9].log - соответствует файлам report_0.logreport_1.log, ... report_9.log.

  • photo_[a-z].jpg - соответствует файлам photo_a.jpgphoto_b.jpg, ..., photo_z.jpg.

Разберём на конкретном примере. У меня в папке есть файлы: notes.txtdata1.csvdata2.csvimage.pngscript.shbackup.tar.gz.

  • Скопировать только CSV-файлы:

rsync -av *.csv /backup/
# Скопирует: data1.csv, data2.csv
  • Исключить все файлы, начинающиеся на "b":

rsync -av --exclude='b*' /source/ /destination/
# Исключит: backup.tar.gz
  • Исключить файлы с однозначной цифрой в названии:

rsync -av --exclude='data?.csv' /source/ /destination/ # ? = 1 символ
# Исключит: data1.csv, data2.csv
# НО не исключит data10.csv (потому что после "data" два символа)

--exclude-from=FILE: массовое исключение из файла

Опция позволяет не перечислять все исключения в командной строке, а «зачитать» их из текстового файла. Каждый шаблон для исключения должен быть на новой строке. Зачем используется?

  • Когда исключений много (десятки пунктов), командная строка становится громоздкой и неудобной для редактирования.

  • Один файл с исключениями (например, .rsync-ignore) можно использовать для множества разных команд rsync, обеспечивая единые правила.

  • Файл исключений можно добавить в Git, чтобы вся команда использовала одинаковые настройки.

Как создать файл исключений? Нам необходимо создать обычный текстовый файл. Например, backup-conf.txt:

# Комментарии rsync проигнорирует
# Исключаем временные файлы
*.tmp
*.swp
.nfs*
# Исключаем системные папки
.Trash-*/
.DS_Store
# Исключаем папки кэша и логи
cache/
logs/*.log
# Исключаем огромную папку (например, в Node.js или Python проекте)
node_modules/
__pycache__/

И, пример использования данного файла:

rsync -av --exclude-from='backup-exclude.txt' ~/important_data/ user@backup-server:/backups/

Стоит отметить: если в файле исключений очень много шаблонов (100-1000+) — это может замедлить работу rsync, так как ему необходимо проверить каждый файл на соответствие каждому шаблону.

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

1.2. Комбинирование --include и --exclude: логика «исключить всё, кроме...»

Иногда простого исключения не хватает для решения задачи. Нужна более сложная логика: «исключить всё, кроме...» или «включить только это, но не то». Для этого используется комбинация --include и --exclude.

Как работает логика: правило «первое совпадение решает»

Очень важно понять: rsync обрабатывает правила фильтрации (--include / --exclude) в том порядке, в котором они указаны в команде.

Алгоритм для каждого файла следующий:

  1. rsync проверяет файл по очереди против каждого правила.

  2. Как только находится первое совпавшее правило, судьба файла решена. Последующие правила игнорируются для этого файла.

  3. Если ни одно правило не совпало, файл включается (копируется).

Это показывает, что порядок опций критически важен.

Практические шаблоны:

Пример 1: «исключить всё, кроме директории src и файлов с расширением .py внутри неё»

Классическийслучай «исключить всё, кроме...». Логика следующая:

  1. Сначала мы включаем то, что хотим сохранить.

  2. Затем исключаем всё остальное.

rsync -av \
    --include='src/' \
    --include='src/***.py' \
    --exclude='*' \
    /source/project/ /backup/project/

Подробный разбор:

  • --include='src/' — включить саму папку src/.

  • --include='src/***.py' — включить все файлы с расширением .py где угодно внутри src/. ** — специальный шаблон для rsync, обозначающий «любое количество поддиректорий на любую глубину». Без него правило сработало бы только для файлов в src/, но не в её подпапках.

  • --exclude='*' — исключить ВСЁ ОСТАЛЬНОЕ. Это правило сработает для любого файла, который не подошёл под два предыдущих правила --include.

Почему порядок важен? Если бы мы поставили --exclude='*' первым, оно бы совпало с абсолютно каждым файлом, и правила --include никогда бы не были проверены. Всё было бы исключено.

Пример 2: «скопировать все картинки, кроме тех, что в папке tmp»

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

rsync -av \
    --include='*/' \
    --include='*.jpg' \
    --include='*.png' \
    --include='*.gif' \
    --exclude='tmp/***' \
    --exclude='*' \
    /source/photos/ /backup/photos/

Подробный разбор:

  • --include='*/' — критически важное правило. Оно включает все директории. Без этого rsync просто не будет заходить в подпапки для их проверки против последующих правил.

  • --include='*.jpg' --include='*.png' --include='*.gif' — включить файлы с нужными расширениями.

  • --exclude='tmp/***' — исключить всё, что находится внутри папки tmp (на любой глубине). Сама папка tmp будет создана (благодаря первому правилу), но её содержимое - нет.

  • --exclude='*' — исключить все файлы, которые не являются картинками (например, .txt, .log и т.д.) и не являются папками.

Как это работает для файла, например /source/photos/tmp/trash.jpg:

  1. Это файл с расширением .jpg, он подходит под правило --include='*.jpg'. Он должен был бы быть включен...

  2. ... но rsync продолжает проверку. Дальше он проверяет путь файла и видит, что он совпадает с шаблоном --exclude='tmp/***'.

  3. Правило --exclude переопределяет предыдущее --include, и файл не копируется.

Важно подвести итог:

  1. Сначала указывайте самые конкретные правила (--include для того, то нужно сохранить, или --exclude для точечных исключений), затем общие (--exclude='*').

  2. Чтобы rsync заходил в подпапки, их нужно явно включить с помощью правила --include='*/'.

  3. Для совпадения путей внутри директорий используйте шаблон ** (две звёздочки), который означает «любая глубина вложенности».

1.3. Профессиональный уровень: файлы фильтров (--filter)

Когда правила включений и исключений становятся слишком сложными для командной строки, нам поможет опция --filter (или её аналог -f), которая позволяет описывать всю логику фильтрации в отдельном файле с помощью компактного и мощного синтаксиса.

Синтаксис файла фильтров: + и -

Каждая строка в файле фильтра - это правило. Правила обрабатываются строго по порядку, и для каждого файла применяется первое же совпавшее правило.

  • + (плюс) - означает ВКЛЮЧИТЬ элемент, соответствующий шаблону.

  • - (минус) - означает ИСКЛЮЧИТЬ элемент, соответствующий шаблону.

  • ! (восклицательный знак) - сбросить (очистить) текущий список фильтров. Используется редко, в очень сложных сценариях.

  • R - правило применяется рекурсивно ко всем вложенным директориям. Например, R - .git/ будет исключать .git в любой поддиректории.

Практический пример: сложный фильтр для бэкапа проекта

Для лучшего понимания, придумаем задачу: сделать бэкап кода веб-проекта, НО:

  1. Исключить системные и временные файлы (.git, node_modules, .DS_Store).

  2. Исключить логи и кэш (*.log, *.tmp, папку cache/).

  3. Исключить тяжелые бинарные зависимости (vendor/, .venv/).

  4. НО включить сам файл requirements.txt или composer.json, который описывает эти зависимости.

  5. НО включить папку public/assets/, даже если она находится внутри исключенной папки (гипотетически).

Создадим файл project-backup.filter (имя может быть любым):

# Файл: project-backup.filter
# ИСКЛЮЧИТЬ системные и временные файлы (правила исключения идут первыми)
- .git/
- .DS_Store
- *.swp
- *.tmp

# ИСКЛЮЧИТЬ тяжелые папки с зависимостями
- node_modules/
- vendor/
- .venv/

# ИСКЛЮЧИТЬ логи и кэш
- *.log
- cache/

# Но ВКЛЮЧИТЬ важные файлы, которые находятся в исключенных директориях
# Правила включения идут после исключений, чтобы их переопределить
+ /vendor/composer.json
+ /vendor/autoload.php
+ /requirements.txt
+ /package-lock.json

# ВКЛЮЧИТЬ папку с ассетами (предполагаем, что она может быть внутри исключенной)
+ /public/assets/***
+ /public/assets/

# Исключить ВСЁ остальное, что не подошло под правила выше
- *

Команда для использования:

rsync -av --filter='merge project-backup.filter' /path/to/project/ user@backup-server:/backups/project/

Ключ merge указывает rsync прочитать правила из указанного файла.

Как это работает (пошагово для файла /project/vendor/composer.json):

  1. rsync проверяет файл vendor/composer.json.

  2. Он проходит по правилам сверху вниз.

  3. Правило - vendor/ совпадает. Файл должен быть исключен.

  4. НО rsync продолжает проверять правила дальше.

  5. Он доходит до правила + /vendor/composer.json.

  6. Это правило переопределяет предыдущее исключение. Файл включается в бэкап.

Рассмотрим основные преимущества данного подхода:

  1. Один файл с фильтрами можно использовать для множества команд rsync и на разных машинах (например, добавить его в репозиторий проекта).

  2. Логика фильтрации собрана в одном хорошо документированном файле, а не размазана по длинной командной строке.

  3. Позволяет описывать невероятно сложные сценарии включения и исключения, которые практически невозможно удобно выразить через --include/--exclude в командной строке.

  4. Файл фильтров можно коммитить в Git, чтобы вся команда синхронизировала и бэкапила данные по единым правилам.

Вывод: использование --filter='merge file' - это профессиональный подход для сложных задач синхронизации и бэкапа. Он превращает rsync из простого инструмента копирования в мощную систему управления переносом данных с детальной настройкой.

Глава 2: экономичные бэкапы с дедупликацией на основе жёстких ссылок

2.1. Как это работает: принцип жёстких ссылок (hard links)

Для понимания принципа работы продвинутых схем бэкапа, важно разобраться с базовым понятием файловой системы - жёсткой ссылкой (hard link).

Краткое объяснение ("один файл на диске, несколько записей в каталогах")

Обычно файл представляется как некий объект, имеющий имя и место хранения в папке. На деле это не совсем так.

  • Файл - это, прежде всего, набор данных на диске (inode).

  • Имя файла в каталоге - всего лишь ссылка (link) на этот набор данных.

Жёсткая ссылка - дополнительное имя (ссылка) для уже существующих данных на диске.

Попробую упростить и описать на аналогии:

> Файл это здание.
> Жёсткая ссылка это дополнительный адрес, по которому можно найти это же самое здание.
> Сколько бы адресов у здания ни было, само здание всегда одно.

Что это значит технически:

  1. Создаём файл A.txt. Файловая система выделяет ему место на диске (inode) и записывает в каталог имя A.txt, которое ссылается на это место.

  2. Создаём жёсткую ссылку B.txt на этот файл: ln A.txt B.txt.

  3. Теперь и A.txt, и B.txt указывают на один и тот же набор данных на диске.

  4. Если изменить содержимое через A.txt, то изменения сразу же появятся и в B.txt, потому что это один и тот же файл.

  5. Если удалить A.txt, данные не удалятся с диска, потому на них всё ещё есть ссылка - B.txt. Данные удалятся только когда будет удалена последняя жёсткая ссылка на них.

Важные свойства жёстких ссылок:

  • Равноправность. Не бывает "оригинала" и "ссылки". Все жёсткие ссылки на один файл абсолютно равноправны.

  • Средствами файловой системы нельзя определить, какое имя было создано первым, а какое является жёсткой ссылкой.

  • Жесткие ссылки можно создавать только в пределах одной файловой системы. Нельзя сделать жёсткую ссылку с диска C: на диск D:.

  • А также, нельзя создать жёсткую ссылку на директорию.

Какое отношение это имеет к rsync и бэкапам?

rsync может использовать жёсткие ссылки с опцией -H ( --hard-links). Это позволяет создавать сложные схемы бэкапа, например, по принципу "зеркало с инкрементальными копиями".

Рассмотрим на примере: делается полный бэкап каждый день. 99% файлов не меняются, но каждый раз место и время на их копирование тратится.

Решение с жёсткими ссылками:

  1. В понедельник делаем полную копию папки backup.mon.

  2. Во вторник вы делаете rsync --link-dest в папку backup.tue.

    • rsync смотрит, какие файлы не изменились со вчерашнего дня.

    • Вместо того чтобы копировать их заново, он создает в backup.tue жёсткие ссылки на те же данные в папке backup.mon.

    • Копируются только новые и изменённые файлы.

Результат:

  • Папка backup.tue выглядит как полная копия на вторник.

  • Но физически на диске она занимает место только под файлы, которые поменялись во вторник + метаданные для всех файлов.

  • Получаем полномасштабные бэкапы на каждый день, которые занимают место как один полный бэкап + все изменения за каждый день.

Таким образом, понимание жёстких ссылок открывает путь к построению эффективных и экономичных систем хранения истории с помощью rsync.

2.2. Практика: создание инкрементальных снимков с --link-dest

Эта опция - "сердце" стратегии "инкрементальных бэкапов с дедупликацией на уровне файлов". Она превращает rsync из простого инструмента копирования в мощную систему управления версиями файлов.

Синтаксис

rsync -aH --link-dest=/path/to/previous/backup /source/ /new/backup/
  • -a: архивный режим (обязателен для сохранения атрибутов файлов, по которым идёт сравнение).

  • -H: сохранять жёсткие ссылки (важно для корректной работы всей цепочки бэкапов).

  • --link-dest=/path/to/previous/backup: указание на абсолютный путь к предыдущему, уже существующему бэкапу.

  • /source/: то бэкапим.

  • /new/backup/: куда будем делать новую копию.

Важное уточнение: путь для --link-dest должен быть абсолютным. Использование относительных путей может привести к неожиданным результатам.

Как это работает

Когда rsync запускается с опцией --link-dest, он выполняет следующий алгоритм для каждого файла в источнике (/source/):

  1. Проверяет существование файла в новой цели (/new/backup/). Если файл уже там есть, он его пропускает (в зависимости от других опций, например, --update).

  2. Если файла в новой цели нет, rsync ищет его в директории, указанной в --link-dest (в нашем примере /path/to/previous/backup).

  3. Если файл найден в --link-dest-директории и он идентичен (проверяется по размеру и временным меткам) файлу в источнике, то:

    • rsync не копирует данные заново.

    • Вместо этого он создаёт в новой целевой директории (/new/backup/) жёсткую ссылку на тот же самый набор данных (inode), что лежит в директории --link-dest.

  4. Если файл не найден в --link-dest или он отличается, то rsync копирует его из источника обычным образом.

Результат: новая папка бэкапа выглядит как полная копия источника, но физически на диске занимает место только под новые и изменённые файлы.

Визуализация на примере

Допустим, мы делаем ежедневные бэкапы папки ~/work/.

День 1 (понедельник): делаем первую полную копию.

rsync -a ~/work/ /backups/work.2023-10-01/
  • Размер: допустим, 10 ГБ.

  • Содержимое: файлы A.txt, B.txt, C.jpg.

День 2 (вторник):

  • В ~/work/ мы изменили только A.txt.

  • Добавили новый файл D.pdf.

  • Файлы B.txt и C.jpg остались без изменений.

Делаем бэкап с --link-dest, указывая на вчерашнюю копию:

rsync -aH --link-dest=/backups/work.2023-10-01/ ~/work/ /backups/work.2023-10-02/

Что произойдёт внутри папки /backups/work.2023-10-02/:

  • A.txt (изменённый): будет скопирован целиком из ~/work/ (так как он отличается от версии в work.2023-10-01).

  • D.pdf (новый): будет скопирован целиком из ~/work/.

  • B.txt (не менялся): rsync создаст в work.2023-10-02 жёсткую ссылку на данные файла B.txt из папки work.2023-10-01.

  • C.jpg (не менялся): аналогично, будет создана жёсткая ссылка на данные из work.2023-10-01.

Итоговый размер: новая папка бэкапа work.2023-10-02 будет занимать на диске место примерно равное размеру изменённого A.txt + размеру нового D.pdf + незначительный вес для метаданных файлов. Вместо 10 ГБ мы можем получить всего 200 МБ.

Вывод: --link-dest - это крайне полезная опция, которая позволяет rsync создавать полные, самодостаточные копии, которые при этом экономят колоссальное количество дискового пространства за счёт повторного использования неизменных данных через жёсткие ссылки. Это основа для построения простых, но эффективных систем бэкапа с длительной историей.

Глава 3: диагностика и решение проблем

3.1. Проблемы с правами доступа (chown)

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

В чём проблема?

Не-root юзер может изменить владельца файла только на самого себя. Если вы запускаете rsync от пользователя backupuser, то все файлы на приёмнике будут принадлежать backupuser, даже если на источнике они принадлежали www-data или root. Это касается и группы

Например: мы хотим скопировать файлы веб-сервера (владелец www-data:www-data) для бэкапа.

backupuser@localhost:$ rsync -a server:/var/www/html/ ./backup/

В папке ./backup/ все файлы будут иметь владельца backupuser:backupuser. Это ломает работоспособность копии, если мы попытаемся использовать её для восстановления, так как веб-серверу www-data может не хватить прав для доступа к файлам, принадлежащим другому пользователю.

Решения

Есть несколько стратегий решения этой проблемы, рассмотрим от самой простой до самой гибкой.

1. Запуск от root (с осторожностью)

Самое прямое решение. Пользователь root может устанавливать любого владельца и группу для файлов. Для этого необходимо запускать команду rsync через sudo. Всё кажется вполне логично, но есть определенные риски. Команда rsync с правами root модет перезаписать критические системные файлы. Нужно быть абсолютно уверенным в путях источника и приёмника. Без права на ошибку.

Пример:

sudo rsync -a server:/var/www/html/ ./backup/
# или для удалённого копирования
sudo rsync -a /source/ user@server:/dest/

2. Использование --usermap и --groupmap

Эти опции позволяют переназначить UID/GID или имена пользователей/групп прямо во время копирования. Это решение для продвинутых сценариев.

  • --usermap=STRING: переназначает владельцев.

  • --groupmap=STRING: переназначает группы.

Рассмотрим пример сценария: на источнике файлы принадлежат www-data, но на системе-приёмнике такого пользователя нет, и вы хотите, чтобы файлы принадлежали пользователю webadmin.

Пример:

rsync -a --usermap=www-data:webadmin --groupmap=www-data:webadmin server:/var/www/ ./backup/

Можно указать несколько правил через запятую и использовать специальные значения:

# Переназначить всё, что принадлежало root, на пользователя backupuser
rsync -a --usermap=*:backupuser --groupmap=*:backupuser /source/ /dest/

3. Сохранение прав через ACL (если не важен конкретный владелец)

Часто важно не именно имя владельца, а права доступа (permissions: read, write, execute). Опция -a (архивный режим) уже включает -p ( --perms), которая сохраняет эти права (например, 755, 644). Этого часто бывает достаточно для бэкапов, если на восстанавливаемой системе потом вручную выставляется нужный владелец.

4. Использование --numeric-ids

Эта опция заставляет rsync работать не с именами пользователей/групп, а с их числовыми UID и GID.

Рассмотрим сценарий: исходная и целевая системы имеют одних и тех же пользователей с одинаковыми UID/GID (например, везде www-data имеет UID=33). Но по каким-то причинам имена на системах различаются.

Пример:

rsync -a --numeric-ids server:/var/www/ ./backup/

На приёмнике файлы будут принадлежать UID=33 и GID=33, а не имени www-data. Если на целевой системе UID 33 также принадлежит пользователю www-data, то всё будет работать корректно.

Итоги:

  • Для точного копирования системных файлов или развёртывания приложений лучше всего запускать rsync от root (через sudo), тщательно проверив команду.

  • Для сложных сценариев миграции между системами с разными пользователями используйте --usermap/--groupmap или --numeric-ids.

3.2. Проблемы с большими директориями (миллионы файлов)

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

В чём проблема?

  1. Время сканирования: перед началом передачи rsync должен построить в памяти список всех файлов на источнике и на приёмнике, чтобы сравнить их. Это операция с линейной, а иногда и квадратичной сложностью. 500 000 файлов - это 500 000 вызовов stat() и операций сравнения.

  2. Потребление памяти: весь этот список файлов и их метаданных должен храниться в оперативной памяти. Чем больше файлов, тем больше памяти требуется. Это может привести к падению rsync с ошибкой out of memory.

  3. Нагрузка на диск: постоянные чтения метаданных (поиск inodes) создают огромную нагрузку на диск (IOPS), особенно если файловая система фрагментирована. Это проявляется как 100% загрузка диска при почти нулевой скорости передачи данных.

Решения и обходные пути

1. Использование --no-recursive и ручной обход

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

Пример: синхронизация директории с множеством подпапок

# Копируем только файлы из корневой директории (не рекурсивно)
rsync -a --no-recursive /source/ /destination/

# Затем находим все поддиректории и запускаем rsync для каждой из них
find /source/ -type d -exec rsync -a {} /destination/ \;

У данного способа есть плюсы и минусы. Рассмотрим:

  • Плюсы: снижает пиковое потребление памяти, позволяет лучше контролировать процесс.

  • Минусы: крайне медленно, так как rsync запускается тысячи раз. Неэффективно для большого количества мелких файлов в одной директории.

2. Упаковка в архив (tar)

Зачастую это лучшее решение для передачи огромного количества мелких файлов по сети. Идея заключается в следующем: на источнике файлы упаковываются в один поток tar, этот поток передаётся по сети и распаковывается на приёмнике.

Пример:

# На источнике: упаковка в tar и передача по SSH
tar -cf - /path/to/source/ | ssh user@host 'cd /destination/ && tar -xf -'

# Или сжатие для экономии трафика
tar -czf - /path/to/source/ | ssh user@host 'cd /destination/ && tar -xzf -'

Плюсы:

  • В разы меньше метаданных: rsync/ssh видит всего один поток данных вместо миллионов файлов.

  • Значительно снижается нагрузка на диск и сеть.

  • На приёмнике будет создано ровно столько inodes, сколько было файлов в архиве. rsync же в некоторых сценариях может создавать временные файлы.

Минусы:

  • Теряется инкрементальность. Передаётся весь объём данных, даже если изменился один файл. Способ не подходит для частых синхронизаций.

  • Нет дедуплекации. Нельзя использовать --link-dest.

3. Гибридный подход (rsync + tar)

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

4. Профилирование и точечная оптимизация

  • Стоит использовать --inplace или --append с осторожностью. Эти опции могут уменьшить нагрузку на диск при обновлении больших файлов, но имеют свои нюансы (к примеру, --inplace ломает возможность докачки и может привести к частично записанным файлам при прерывании).

  • Поиск "виновников"... Иногда проблема не в количестве файлов, а в нескольких конкретных директориях (к примеру, node_modules или .git). Стоит использовать --exclude чтобы исключить их из синхронизации.

rsync -a --exclude='node_modules/' --exclude='.git/' /source/ user@host:/dest/

Итог:

Если rsync "подвисает" на этапе построения файлового списка и почти не использует сеть/диск для передачи - это верный признак проблемы с большим количеством файлов.

  • Для инкрементальной синхронизации: пытайтесь исключать лишние поддиректории и смиритесь с долгой работой.

  • Для разового переноса или редких бэкапов: используйте tar. Это почти всегда будет быстрее и надёжнее для каталогов с более чем 100-200 тысячами файлов.

  • Для постоянной синхронизации: рассмотрите использование других инструментов, заточенных под огромное количество inodes (например, unison или специализированные ФС для бэкапов типа restic/borg).


Когда rsync обрабатывает сотни тысяч мелких файлов, основное время уходит не на пересылку данных (которая минимальна), а на метаданные. Это создаёт хаотичный шаблон доступа к диску.

В чём проблема?

  1. Для каждого файла rsync должен:

    • Прочитать его метаданные (inode) на источнике.

    • Проверить, существует ли он на приёмнике (ещё одно чтение метаданных).

    • Сравнить метаданные (размер, время модификации, контрольную сумму).

    • Если файл изменился - прочитать его содержимое.

    • Записать метаданные и данные на приёмник.

  2. Эти операции заставляют считывающие головки диска постоянно "прыгать" по всей площади пластин в поисках нужных inodes и блоков данных. Для традиционных HDD (жестких дисков) это смертельно, так как время позиционирования (seek time) составляет миллисекунды на каждый файл. Суммарно это приводит к гигантским задержкам.

  3. IOPS (Input/Output Operations Per Second): проблема упирается в лимит операций ввода-вывода в секунду, который у HDD очень низок для случайных операций (десятки-сотни IOPS).

Как это мониторить? (iostat)

Мы можем мониторить нагрузку на диски можно с помощью утилиты iostat.

# Смотрим расширенную статистику по всем дискам каждую секунду
iostat -x 1

На что смотреть в выводе:

  • %util: процент утилизации диска. Значение близкое к 100% говорит о том, что диск - узкое место.

  • await (ms): среднее время ожидания I/O-операции. Высокие значения (например, >20 мс для HDD) говорят о перегруженности.

  • r/s, w/s: количество операций чтения и записи в секунду. При работе с мелкими файлами эти значения будут очень высокими, а avgrq-sz (средний размер запроса) - низким.

Пример вывода, показывающего проблему:

Device: ... rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await %util
sda    ... 0.00   0.00   850 150 5000  3000  10.05    15.00    50.00  45.00  60.00   100.00

Здесь видно, что диск sda загружен на 100% (%util), при этом средний размер запроса всего ~10 секторов (~5 КБ) (avgrq-sz), а количество операций в секунду очень высокое (r/s + w/s = 1000). Это классическая картина работы с мелкими файлами.

Как минимизировать проблему?

1. Приоритет: использовать SSD
Это кардинальное, но самое эффективное решение. SSD практически не имеют задержек на случайный доступ, поэтому проблема "прыгающих" головок для них неактуальна. Количество IOPS у SSD на порядки выше.

2. Увеличить размер запроса

  • --inplace: может помочь в некоторых сценариях, так как rsync будет меньше перезаписывать файлы целиком.

  • --whole-file (-W): заставляет rsync копировать файлы целиком, а не дельтами. Это уменьшает количество операций чтения на источнике (не нужно вычислять разницы), но не решает проблему с метаданными. Эффективно в быстрых LAN-сетях.

3. Изменить метод передачи
Как и в предыдущем пункте, лучшим решением для передачи огромного количества мелких файлов является упаковка в tar.

Почему это может нам помочь? tar упаковывает все файлы и их метаданные в один непрерывный поток. Дисковые головки на источнике читают данные последовательно, а на приёмнике - записывают один большой файл или распаковывают его тоже последовательно. Количество случайных операций (IOPS) сводится к минимуму.

Приведу пример:

# Вместо этого (создаёт высокую IOPS-нагрузку):
rsync -a /source/with/many/files/ user@host:/dest/

# Сделать это (создаёт низкую IOPS-нагрузку):
tar -cf - -C /source/with/many/files . | ssh user@host 'tar -xf - -C /dest/'

Вывод: если мы видим с помощью iostat, что диск "тонет" в тысячах мелких операций ввода-вывода при работе rsync, нет смысла ждать чудо. Необходимо остановиться и изменить стратегию. Из вариантов, упаковать данные в архив или смириться с тем, что операция займет очень много времени. Во втором случае имеет смысл запустить её с низким приоритетом.

3.3. Проблемы с прерванной передачей: докачка и проверка целостности

Поведение rsync по умолчанию при прерывании передачи довольно категоричное: он удаляет не до конца переданный файл на стороне приёмника. Сделано это для того, чтобы избежать ситуации с частичными, "битыми" файлами, которые могут быть приняты за целые. К счастью, этим поведением можно управлять.

Поведение по умолчанию (проблема)

По классике, придумаем сценарий:

  1. Мы передаём большой файл movie.mkv (допустим, 10 ГБ).

  2. На 8 ГБ соединение обрывается.

  3. rsync завершает работу с ошибкой.

  4. При следующем запуске rsync видит, что на приёмнике лежит файл movie.mkv размером 8 ГБ, который не соответствует файлу на источнике.

  5. Чтобы начать передачу "с чистого листа", rsync удаляет этот частичный файл и начинает копирование сначала.

Это крайне неэффективно, так как мы теряем весь прогресс и вынуждены повторно передавать уже пересланные гигабайты.

Решение: Флаги --partial --append

Комбинация этих двух флагов кардинально меняет логику работы rsync с частично переданными файлами.

  • --partial: сообщает rsync НЕ УДАЛЯТЬ частично переданные файлы при прерывании. Они остаются на приёмнике.

  • --append: сообщает rsync ПРОДОЛЖИТЬ передачу частично переданного файла с места обрыва. rsync проверяет, какой объём файла уже есть на приёмнике, и начинает передачу с соответствующего места.

Как это работает:

  1. Мы передаём большой файл movie.mkv (10 ГБ).

  2. На 8 ГБ соединение обрывается. Файл movie.mkv размером 8 ГБ остаётся на приёмнике (благодаря --partial).

  3. Мы запускаем ту же самую команду rsync снова.

  4. rsync видит, что на приёмнике уже есть файл movie.mkv размером 8 ГБ.

  5. Благодаря --append, он не начинает передачу сначала, а проверяет, совпадают ли первые 8 ГБ файла на источнике с тем, что уже есть на приёмнике.

  6. Если начало файла совпадает (проверяется быстро), rsync докачивает только оставшиеся 2 ГБ.

Пример команды для нестабильного соединения:

rsync -av --progress --partial --append /path/to/source/movie.mkv user@remote-host:/path/to/dest/

Важные нюансы и ограничения

  1. --append проверяет только размер. Этот флаг предполагает, что уже переданная часть файла идентична началу файла на источнике. Он не проверяет контрольные суммы уже переданных данных. Если по какой-то причине содержимое переданной части было повреждено (например, из-за бага на диске приёмника), то итоговый файл также будет повреждён.

  2. Не для всех случаев. Эта стратегия идеальна для больших однофайловых объектов (образы дисков, архивы, видео), которые не изменяются на источнике во время передачи. Для часто меняющихся файлов или директорий с множеством мелких файлов использование --append может быть небезопасным.

  3. Альтернатива: --partial-dir. Более продвинутый вариант - использовать --partial-dir=DIR (было упомянуто выше). В этом случае частичные файлы сохраняются не в целевую директорию, а в указанную временную папку (например, --partial-dir=.rsync-partial). После успешной полной передачи файл перемещается наFinalное место. Это предотвращает появление "битых" файлов в основной папке.

А также, немного про "пропуск файлов"... rsync должен быть быстрым. Поэтому по умолчанию он использует простой и быстрый эвристический метод, чтобы определить, изменился ли файл. Но эта скорость достигается за счёт возможной неточности.

Алгоритм сравнения по умолчанию (быстрый, но ненадёжный)

По умолчанию rsync использует два критерия для принятия решения о необходимости копирования файла:

  1. Размер файла на источнике и на приёмнике должен совпадать.

  2. Временная метка последнего изменения файла на источнике должна быть не старше, чем на приёмнике.

Если оба этих условия выполняются, rsync считает файл идентичным и пропускает его.

Попробуем разобраться, почему этот алгоритм может ошибаться.

Сценарий 1: файл изменился, но mtime остался прежним.

  • Это частая ситуация при восстановлении файлов из бэкапа, клонировании репозитория или ручном редактировании с помощью инструментов, которые не обновляют mtime (например, некоторые версии sed -i). Файл другой, но rsync видит одинаковый размер и дату и решает, что менять ничего не нужно.

Сценарий 2: mtime "скачет" в будущее или прошлое**

  • Например, файлы были распакованы из архива, где сохранились старые временные метки. Или часы на машинах показывают разное время. Файл на источнике может быть новее, но из-за рассинхронизации часов rsync посчитает его старым и не станет обновлять.

Сценарий 3: коллизия размера и mtime.

  • Теоретически возможна ситуация, когда два разных файла имеют одинаковый размер и время модификации. rsync ошибочно сочтёт их идентичными.

Что такое mtime?

mtime (modification time) - временная метка файла, которая указывает на время последнего изменения содержимого файла. Когда Вы добавляете, удаляете или изменяете данные в файле, его mtime обновляется. Эта метка не меняется при изменении других свойств файла, таких как права доступа, владелец или группа.

Решение: проверка контрольных сумм (-c / --checksum)

Чтобы заставить rsync проверять файлы на реальную идентичность, используется флаг -c ( --checksum). Делает следующее: rsync вычисляет контрольную сумму для каждого файла на источнике и на приёмнике. Файлы считаются идентичными только если их контрольные суммы полностью совпадают. Это единственный "стопроцентно" надёжный способ определить, изменился ли файл. Он никак не зависит от времени, часов, метаданных.

Обратная сторона: почему -c тормозит работу в 100 раз

Недостаток этого метода - катастрофическое падение производительности. Вот почему:

  1. Для расчета контрольной суммы rsync должен прочитать каждый файл целиком на обеих системах — и на источнике, и на приёмнике. Это создаёт огромную нагрузку на диски (см. пункт 6.5 про IOPS).

  2. Алгоритм вычисления хэша (обычно MD5 или xxHash) требует процессорного времени для каждого файла.

  3. При работе с удалённым сервером контрольные суммы должны вычисляться на обеих сторонах, а результаты - сравниваться, что добавляет сетевые задержки.

Попробуем сравнить:

  • Без -c: rsync делает быстрый системный вызов stat() для каждого файла, чтобы получить его размер и дату. Это операция с метаданными, она очень быстрая.

  • С -c: rsync читает всё содержимое каждого файла. Для файла размером 1 ГБ это означает необходимость прочитать 1 ГБ данных только для того, чтобы решить, нужно ли его читать для передачи.

Когда использовать --checksum?

Из-за огромных накладных расходов флаг -c следует использовать только в особых случаях:

  1. Для критически важных данных, где вероятность ошибки неприемлема (например, бэкап базы данных).

  2. Когда есть подозрение, что файлы могли измениться без обновления mtime.

  3. При синхронизации с файловыми системами или системами, которые ненадёжно работают с временными метками (например, некоторые сетевые ФС, FAT32).

  4. Для проверки целостности уже синхронизированных данных.

Пример команды для гарантированной идентичности:

rsync -avc --progress /source/ user@host:/destination/

Практический совет: Начинайте всегда с обычной синхронизации (rsync -av). Если вы заметили, что какие-то файлы не обновляются, хотя должны бы, тогда запустите ту же команду с флагом -c, чтобы принудительно проверить и переслать всё необходимое. Используйте --checksum выборочно, а не по умолчанию.

3.4. Сжатие vs шифрование: когда -z не помогает, а вредит

Общее правило «использовать -z для передачи по сети» верно, но оно имеет критически важные исключения. Главный подводный камень - взаимодействие сжатия и шифрования.

Как это работает (упрощённо):

  1. rsync -z (сжатие): данные сжимаются "на лету" перед отправкой в сеть.

  2. SSH (шифрование): данные шифруются перед отправкой в сеть.

Когда вы используете rsync поверх SSH, конвейер выглядит так:
Данные -> rsync (сжатие) -> SSH (шифрование) -> Сеть

Когда -z замедляет передачу (и ест CPU)?

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

Сценарий 1: передача уже сжатых или зашифрованных данных
Это главный случай, когда -z бесполезен и вреден.

  • Что передаём: архивы (.zip, .tar.gz, .7z), медиа (.jpg, .mp4, .mp3), исполняемые файлы (бинарники, .deb, .rpm), уже зашифрованные данные.

  • Что происходит: rsync тратит процессорное время и задержки (CPU time) на попытку сжать уже сжатые данные. В лучшем случае степень сжатия будет около 1:1 (0%), в худшем - данные "раздуются" из-за накладных расходов алгоритма сжатия.

  • Результат: мы получаем дополнительную нагрузку на CPU на обеих сторонах (сжатие/расжатие) без какой-либо экономии трафика. Общее время передачи увеличивается, так как время на обработку становится больше, чем время на пересылку несжатых данных.

Сценарий 2: очень быстрая сеть и медленный CPU

  • Что имеем: гигабитная или более быстрая локальная сеть (LAN) и относительно слабые процессоры на клиенте и сервере (например, старые NAS, маломощные VPS).

  • Что происходит: скорость обработки (сжатия) данных процессором становится "бутылочным горлышком", которое не позволяет насытить быстрый сетевой канал. Данные ждут своей очереди на сжатие, вместо того чтобы уже лететь по сети.

  • Результат: без сжатия данные просто "льются" на максимальной скорости сети. Со сжатием CPU не успевает их готовить, и общая скорость передачи падает.

Когда -z ускоряет передачу (и экономит трафик)?

Это классический случай, для которого опция и была создана.

  • Что передаём: текстовые файлы (код, .html, .css, .js, конфиги, логи), несжатые базы данных, файлы документов (.txt, .xml, .json).

  • Что происходит: эти типы данных отлично сжимаются (иногда в 5-10 раз и больше). Экономия трафика огромна.

  • Результат даже с учётом времени на сжатие/расжатие общее время передачи значительно сокращается, особенно на медленных каналах (интернет). Нагрузка на CPU оправдана значительной экономией времени.

Практическое правило

Тип данных

Использовать -z?

Почему?

Текст, код, логи, XML/JSON

Да, обязательно

Высокий коэффициент сжатия. Большая экономия времени.

Фотографии (JPG, PNG), видео (MP4, AVI), музыка (MP3)

Нет

Файлы уже сжаты. Дополнительное сжатие бесполезно.

Архивы (ZIP, GZ, RAR)

Нет

Файлы уже сжаты. Дополнительное сжатие бесполезно.

Бинарники, исполняемые файлы

Скорее нет

Обычно плохо сжимаются. Лучше проверить на конкретном наборе.

Смешанное содержимое

Зависит от ситуации

Если большая часть - текст, то -z поможет. Если в основном медиа - нет.

Вывод: не стоит использовать -z вслепую для всего подряд. Важно учитывать "природу" передаваемых данных. Слепо применять -z для бэкапа, содержащего много медиа и архивов - верный способ создать лишнюю нагрузку на систему без даже малейшей выгоды.

3.5. Мониторинг производительности: ищем «узкое место»

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

1. Мониторинг процессора (top, htop)

top # или htop (является более наглядной утилитой)

На что обратить внимание?

  1. %Cpu(s): us (user): показатель загрузки CPU пользовательскими процессами. Высокое значение говорит о том, что rsync (или другие процессы) активно используют процессор.

  2. %Cpu(s): sy (system): показатель загрузки CPU системными вызовами. Может быть высоким, если rsync заставляет ядро работать интенсивно (например, при обработке метаданных миллионов файлов).

  3. %Cpu(s): wa (i/o wait): наиболее важный показатель для rsync. Он показывает, какую долю времени CPU простаивает в ожидании завершения операций ввода-вывода (диск, сеть). Если wa высокий (например, >20%), значит, система уперлась в скорость дисков или сети, и процессор просто ждет данных.

Вывод:

  • Высокий us/sy: rsync нагружает процессор (возможно, из-за -c (checksum) или сжатия -z).

  • Высокий wa: узкое место - диск или сеть.

2. Мониторинг дискового ввода-вывода (iotop, iostat)

sudo iotop -o # флаг -o покажет только активные процессы

На что обратить внимание в iotop?

  • DISK READ и DISK WRITE: скорость чтения и записи для каждого процесса. У rsync мы должны видеть активность.

  • IO>: текущая нагрузка на диск в процентах. Показывает, насколько процесс "забивает" канал дисковой подсистемы.

  • Смотрим на общую картину: если rsync показывает высокие скорости чтения/записи - он уперся в пределы диска. Если скорости низкие, а wa в top высокий - значит, проблема в IOPS (см. пункт 6.5), т.е. диск не успевает обрабатывать множество мелких запросов.

Вывод:

  • Высокие MB/s: rsync уперся в пропускную способность диска.

  • Высокие r/s + w/s при низких MB/s: rsync "уперся" в IOPS (обрабатывает множество мелких файлов).

3. Мониторинг сети (nethogs, iftop)

sudo nethogs 
# или
sudo iftop # покажет трафик между хостами

На что обратить внимание в nethogs?

  • Утилита группирует трафик по процессам. Мы сразу увидим, сколько трафика rsync генерирует в секциях Sent и Received.

  • В iftop смотрим на общий трафик на интерфейсе (вверху) и на то, какие хосты и с какой скоростью общаются.

Вывод:

  • Скорость сети близка к максимальной (например, 95-98 Мбит/с на гигабитном канале): rsync уперся в пропускную способность сети. В этом случае сжатие (-z) может снизить общую производительность, так как оно создаст дополнительную нагрузку на CPU, но не даст прироста скорости, потому что мы уже упираемся в пределы сетевого интерфейса. Мы и так "качаем на максимум".

  • Скорость сети низкая, но rsync активен: узкое место - не сеть, а диск или CPU. rsync просто не успевает готовить данные для передачи. Вот здесь как раз поможет сжатие (-z). Если "бутылочное горлышко" - это CPU на подготовку данных (checksum, чтение с диска), то сжатие не решит проблему. Но если сеть медленная (например, канал 10 Мбит/с), а данные хорошо сжимаются (тексты, логи, код), то сжатие позволит уменьшить объем передаваемых данных и, следовательно, повысит эффективность использования медленного канала, ускорив передачу.

Сводная таблица диагностики и решений

Симптом (что показывают утилиты)

Вероятная причина

Возможное решение

top: высокий wa iotop: высокие r/s, w/s, низкие MB/s

Проблема IOPS: слишком много мелких файлов. Диск "прыгает" между ними.

Исключить лишние файлы (--exclude), использовать tar для упаковки, апгрейд на SSD.

top: высокий wa iotop: высокие MB/s

Диск не успевает читать/записывать большие файлы.

Сложно решить. Возможно, другие процессы грузят диск. Запустить rsync с ionice.

top: высокий us nethogs: низкая скорость сети

Узкое место - CPU: процессор не успевает шифровать (ssh), сжимать (-z) или считать хэши (-c).

Убрать -c или -z, если возможно. Апгрейд CPU.

top: низкий wa, низкий us nethogs: низкая скорость сети

Узкое место - сеть: данные передаются медленно из-за лимита канала или потерь.

Использовать -z для сжатия, проверить качество сети, увеличить --bwlimit если он установлен.

nethogs: скорость сети близка к макс.

Идеальная ситуация: rsync эффективно использует доступный канал.

Ничего не делать, ждать завершения.

Подведу итог. Не гадайте, почему rsync медленный. Откройте три терминала, запустите в них htop, sudo iotop -o и sudo nethogs и Вы сразу увидите слабое звено в системе. Это знание подскажет, какие опции rsync можно применить для ускорения работы.

Заключение

Полученных в этих статьях знаний - достаточно для подавляющего количества повседневных задач. Но если rsync перестанет покрывать Ваши возможности - например, Вам потребуется сквозное шифрование, глобальная дедупликация или более сложное управление версиями бэкапов - стоит присмотреться к аналогам: BorgBackup, Restic, Rclone. О них я расскажу в следующей, заключительной статье (кратко, как логичное завершение серии).

P.S. В моей группе в Телеграмм разбираем практические кейсы: скрипты (Python/Bash/PowerShell), тонкости ОС и инструменты для эффективной работы.

Комментарии (0)


  1. Looka
    19.09.2025 17:06

    • Скорость сети близка к максимальной (например, 95-98 Мбит/с на гигабитном канале): rsync уперся в пропускную способность сети. Поможет сжатие (-z), если данные хорошо сжимаются.

    • Скорость сети низкая, но rsync активен: узкое место - не сеть, а диск или CPU. rsync просто не успевает готовить данные для передачи.


    С точность до наоборот, поправьте


    1. eternaladm Автор
      19.09.2025 17:06

      Спасибо за комментарий! Верно подмечено, оговорился. Спасибо, исправлено!


  1. ilyailyailya
    19.09.2025 17:06

    В некоторых блоках code, в том числе и внутри параграфов некорректно указанны коды HTML вместо специальных символов (& --> & и т. п.). На таких примерах ничего хорошего не получится.