Привет, Хабр! В первой части я разобрал основы rsync
: синтаксис, ключевые опции и работу по SSH. Эти аспекты позволяют эффективно пользоваться утилитой на базовом уровне. В этой статье заглянем «под капот» и научимся тонко контролировать весь процесс синхронизации и диагностировать проблемы с производительностью...
Глава 1: исключения и фильтры — тотальный контроль над синхронизацией
1.1. Базовые исключения: --exclude и --exclude-from
Иногда возникает необходимость скопировать не всё подряд, а всё кроме некоторых файлов или папок. rsync
предоставляет для этого мощный и гибкий механизм исключений.
--exclude=PATTERN: исключение по шаблону
Опция позволяет указать один шаблон (pattern) для файлов или директорий, которые нужно пропустить при синхронизации. Шаблоны похожи на упрощённые регулярные выражения. Рассмотрим их подробнее:
1. Символ *
: заменяет любую последовательность любых символов (включая отсутствие символов).
*.txt
- соответствует всем файлам, которые заканчиваются на.txt
(например,file.txt
,document.txt
,1.txt
).project*
- соответствует всем файлам, которые начинаются наproject
(например,project
,project1
,project_backup
).data
- соответствует всем файлам, которые содержат в названии словоdata
(например,data
,mydata
,database_backup
,old_data_file.log
).
2. Символ ?
: заменяет ровно один любой символ.
file?.txt
- соответствует файламfile1.txt
,fileA.txt
,file_.txt
, но неfile10.txt
(потому что там два символа вместо одного) и неfile.txt
(потому что послеfile
должен быть хоть один символ).
3. Символ [ ]
: заменяет один символ из тех, что перечислены внутри скобок. Возможно задавать диапазоны.
file[123].txt
- соответствует только файламfile1.txt
,file2.txt
,file3.txt
.report_[0-9].log
- соответствует файламreport_0.log
,report_1.log
, ...report_9.log
.photo_[a-z].jpg
- соответствует файламphoto_a.jpg
,photo_b.jpg
, ...,photo_z.jpg
.
Разберём на конкретном примере. У меня в папке есть файлы: notes.txt
, data1.csv
, data2.csv
, image.png
, script.sh
, backup.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
) в том порядке, в котором они указаны в команде.
Алгоритм для каждого файла следующий:
rsync
проверяет файл по очереди против каждого правила.Как только находится первое совпавшее правило, судьба файла решена. Последующие правила игнорируются для этого файла.
Если ни одно правило не совпало, файл включается (копируется).
Это показывает, что порядок опций критически важен.
Практические шаблоны:
Пример 1: «исключить всё, кроме директории src
и файлов с расширением .py
внутри неё»
Классическийслучай «исключить всё, кроме...». Логика следующая:
Сначала мы включаем то, что хотим сохранить.
Затем исключаем всё остальное.
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
:
Это файл с расширением
.jpg
, он подходит под правило--include='*.jpg'
. Он должен был бы быть включен...... но
rsync
продолжает проверку. Дальше он проверяет путь файла и видит, что он совпадает с шаблоном--exclude='tmp/***'
.Правило
--exclude
переопределяет предыдущее--include
, и файл не копируется.
Важно подвести итог:
Сначала указывайте самые конкретные правила (
--include
для того, то нужно сохранить, или--exclude
для точечных исключений), затем общие (--exclude='*'
).Чтобы
rsync
заходил в подпапки, их нужно явно включить с помощью правила--include='*/'
.Для совпадения путей внутри директорий используйте шаблон
**
(две звёздочки), который означает «любая глубина вложенности».
1.3. Профессиональный уровень: файлы фильтров (--filter)
Когда правила включений и исключений становятся слишком сложными для командной строки, нам поможет опция --filter
(или её аналог -f
), которая позволяет описывать всю логику фильтрации в отдельном файле с помощью компактного и мощного синтаксиса.
Синтаксис файла фильтров: + и -
Каждая строка в файле фильтра - это правило. Правила обрабатываются строго по порядку, и для каждого файла применяется первое же совпавшее правило.
+
(плюс) - означает ВКЛЮЧИТЬ элемент, соответствующий шаблону.-
(минус) - означает ИСКЛЮЧИТЬ элемент, соответствующий шаблону.!
(восклицательный знак) - сбросить (очистить) текущий список фильтров. Используется редко, в очень сложных сценариях.R
- правило применяется рекурсивно ко всем вложенным директориям. Например,R - .git/
будет исключать.git
в любой поддиректории.
Практический пример: сложный фильтр для бэкапа проекта
Для лучшего понимания, придумаем задачу: сделать бэкап кода веб-проекта, НО:
Исключить системные и временные файлы (
.git
,node_modules
,.DS_Store
).Исключить логи и кэш (
*.log
,*.tmp
, папкуcache/
).Исключить тяжелые бинарные зависимости (
vendor/
,.venv/
).НО включить сам файл
requirements.txt
илиcomposer.json
, который описывает эти зависимости.НО включить папку
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):
rsync
проверяет файлvendor/composer.json
.Он проходит по правилам сверху вниз.
Правило
- vendor/
совпадает. Файл должен быть исключен.НО
rsync
продолжает проверять правила дальше.Он доходит до правила
+ /vendor/composer.json
.Это правило переопределяет предыдущее исключение. Файл включается в бэкап.
Рассмотрим основные преимущества данного подхода:
Один файл с фильтрами можно использовать для множества команд
rsync
и на разных машинах (например, добавить его в репозиторий проекта).Логика фильтрации собрана в одном хорошо документированном файле, а не размазана по длинной командной строке.
Позволяет описывать невероятно сложные сценарии включения и исключения, которые практически невозможно удобно выразить через
--include
/--exclude
в командной строке.Файл фильтров можно коммитить в Git, чтобы вся команда синхронизировала и бэкапила данные по единым правилам.
Вывод: использование --filter='merge file'
- это профессиональный подход для сложных задач синхронизации и бэкапа. Он превращает rsync
из простого инструмента копирования в мощную систему управления переносом данных с детальной настройкой.
Глава 2: экономичные бэкапы с дедупликацией на основе жёстких ссылок
2.1. Как это работает: принцип жёстких ссылок (hard links)
Для понимания принципа работы продвинутых схем бэкапа, важно разобраться с базовым понятием файловой системы - жёсткой ссылкой (hard link).
Краткое объяснение ("один файл на диске, несколько записей в каталогах")
Обычно файл представляется как некий объект, имеющий имя и место хранения в папке. На деле это не совсем так.
Файл - это, прежде всего, набор данных на диске (inode).
Имя файла в каталоге - всего лишь ссылка (link) на этот набор данных.
Жёсткая ссылка - дополнительное имя (ссылка) для уже существующих данных на диске.
Попробую упростить и описать на аналогии:
> Файл это здание.
> Жёсткая ссылка это дополнительный адрес, по которому можно найти это же самое здание.
> Сколько бы адресов у здания ни было, само здание всегда одно.
Что это значит технически:
Создаём файл
A.txt
. Файловая система выделяет ему место на диске (inode) и записывает в каталог имяA.txt
, которое ссылается на это место.Создаём жёсткую ссылку
B.txt
на этот файл:ln A.txt B.txt
.Теперь и
A.txt
, иB.txt
указывают на один и тот же набор данных на диске.Если изменить содержимое через
A.txt
, то изменения сразу же появятся и вB.txt
, потому что это один и тот же файл.Если удалить
A.txt
, данные не удалятся с диска, потому на них всё ещё есть ссылка -B.txt
. Данные удалятся только когда будет удалена последняя жёсткая ссылка на них.
Важные свойства жёстких ссылок:
Равноправность. Не бывает "оригинала" и "ссылки". Все жёсткие ссылки на один файл абсолютно равноправны.
Средствами файловой системы нельзя определить, какое имя было создано первым, а какое является жёсткой ссылкой.
Жесткие ссылки можно создавать только в пределах одной файловой системы. Нельзя сделать жёсткую ссылку с диска
C:
на дискD:
.А также, нельзя создать жёсткую ссылку на директорию.
Какое отношение это имеет к rsync и бэкапам?
rsync
может использовать жёсткие ссылки с опцией -H
( --hard-links
). Это позволяет создавать сложные схемы бэкапа, например, по принципу "зеркало с инкрементальными копиями".
Рассмотрим на примере: делается полный бэкап каждый день. 99% файлов не меняются, но каждый раз место и время на их копирование тратится.
Решение с жёсткими ссылками:
В понедельник делаем полную копию папки
backup.mon
.-
Во вторник вы делаете
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/
):
Проверяет существование файла в новой цели (
/new/backup/
). Если файл уже там есть, он его пропускает (в зависимости от других опций, например,--update
).Если файла в новой цели нет,
rsync
ищет его в директории, указанной в--link-dest
(в нашем примере/path/to/previous/backup
).-
Если файл найден в
--link-dest
-директории и он идентичен (проверяется по размеру и временным меткам) файлу в источнике, то:rsync
не копирует данные заново.Вместо этого он создаёт в новой целевой директории (
/new/backup/
) жёсткую ссылку на тот же самый набор данных (inode), что лежит в директории--link-dest
.
Если файл не найден в
--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
- не волшебная палочка. При работе с директориями, содержащими сотни тысяч или миллионы файлов, он упирается в ограничения производительности.
В чём проблема?
Время сканирования: перед началом передачи
rsync
должен построить в памяти список всех файлов на источнике и на приёмнике, чтобы сравнить их. Это операция с линейной, а иногда и квадратичной сложностью. 500 000 файлов - это 500 000 вызововstat()
и операций сравнения.Потребление памяти: весь этот список файлов и их метаданных должен храниться в оперативной памяти. Чем больше файлов, тем больше памяти требуется. Это может привести к падению
rsync
с ошибкойout of memory
.Нагрузка на диск: постоянные чтения метаданных (поиск 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
обрабатывает сотни тысяч мелких файлов, основное время уходит не на пересылку данных (которая минимальна), а на метаданные. Это создаёт хаотичный шаблон доступа к диску.
В чём проблема?
-
Для каждого файла
rsync
должен:Прочитать его метаданные (inode) на источнике.
Проверить, существует ли он на приёмнике (ещё одно чтение метаданных).
Сравнить метаданные (размер, время модификации, контрольную сумму).
Если файл изменился - прочитать его содержимое.
Записать метаданные и данные на приёмник.
Эти операции заставляют считывающие головки диска постоянно "прыгать" по всей площади пластин в поисках нужных inodes и блоков данных. Для традиционных HDD (жестких дисков) это смертельно, так как время позиционирования (seek time) составляет миллисекунды на каждый файл. Суммарно это приводит к гигантским задержкам.
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
по умолчанию при прерывании передачи довольно категоричное: он удаляет не до конца переданный файл на стороне приёмника. Сделано это для того, чтобы избежать ситуации с частичными, "битыми" файлами, которые могут быть приняты за целые. К счастью, этим поведением можно управлять.
Поведение по умолчанию (проблема)
По классике, придумаем сценарий:
Мы передаём большой файл
movie.mkv
(допустим, 10 ГБ).На 8 ГБ соединение обрывается.
rsync
завершает работу с ошибкой.При следующем запуске
rsync
видит, что на приёмнике лежит файлmovie.mkv
размером 8 ГБ, который не соответствует файлу на источнике.Чтобы начать передачу "с чистого листа",
rsync
удаляет этот частичный файл и начинает копирование сначала.
Это крайне неэффективно, так как мы теряем весь прогресс и вынуждены повторно передавать уже пересланные гигабайты.
Решение: Флаги --partial --append
Комбинация этих двух флагов кардинально меняет логику работы rsync
с частично переданными файлами.
--partial
: сообщаетrsync
НЕ УДАЛЯТЬ частично переданные файлы при прерывании. Они остаются на приёмнике.--append
: сообщаетrsync
ПРОДОЛЖИТЬ передачу частично переданного файла с места обрыва.rsync
проверяет, какой объём файла уже есть на приёмнике, и начинает передачу с соответствующего места.
Как это работает:
Мы передаём большой файл
movie.mkv
(10 ГБ).На 8 ГБ соединение обрывается. Файл
movie.mkv
размером 8 ГБ остаётся на приёмнике (благодаря--partial
).Мы запускаем ту же самую команду
rsync
снова.rsync
видит, что на приёмнике уже есть файлmovie.mkv
размером 8 ГБ.Благодаря
--append
, он не начинает передачу сначала, а проверяет, совпадают ли первые 8 ГБ файла на источнике с тем, что уже есть на приёмнике.Если начало файла совпадает (проверяется быстро),
rsync
докачивает только оставшиеся 2 ГБ.
Пример команды для нестабильного соединения:
rsync -av --progress --partial --append /path/to/source/movie.mkv user@remote-host:/path/to/dest/
Важные нюансы и ограничения
--append
проверяет только размер. Этот флаг предполагает, что уже переданная часть файла идентична началу файла на источнике. Он не проверяет контрольные суммы уже переданных данных. Если по какой-то причине содержимое переданной части было повреждено (например, из-за бага на диске приёмника), то итоговый файл также будет повреждён.Не для всех случаев. Эта стратегия идеальна для больших однофайловых объектов (образы дисков, архивы, видео), которые не изменяются на источнике во время передачи. Для часто меняющихся файлов или директорий с множеством мелких файлов использование
--append
может быть небезопасным.Альтернатива:
--partial-dir
. Более продвинутый вариант - использовать--partial-dir=DIR
(было упомянуто выше). В этом случае частичные файлы сохраняются не в целевую директорию, а в указанную временную папку (например,--partial-dir=.rsync-partial
). После успешной полной передачи файл перемещается наFinalное место. Это предотвращает появление "битых" файлов в основной папке.
А также, немного про "пропуск файлов"... rsync
должен быть быстрым. Поэтому по умолчанию он использует простой и быстрый эвристический метод, чтобы определить, изменился ли файл. Но эта скорость достигается за счёт возможной неточности.
Алгоритм сравнения по умолчанию (быстрый, но ненадёжный)
По умолчанию rsync
использует два критерия для принятия решения о необходимости копирования файла:
Размер файла на источнике и на приёмнике должен совпадать.
Временная метка последнего изменения файла на источнике должна быть не старше, чем на приёмнике.
Если оба этих условия выполняются, 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 раз
Недостаток этого метода - катастрофическое падение производительности. Вот почему:
Для расчета контрольной суммы
rsync
должен прочитать каждый файл целиком на обеих системах — и на источнике, и на приёмнике. Это создаёт огромную нагрузку на диски (см. пункт 6.5 про IOPS).Алгоритм вычисления хэша (обычно MD5 или xxHash) требует процессорного времени для каждого файла.
При работе с удалённым сервером контрольные суммы должны вычисляться на обеих сторонах, а результаты - сравниваться, что добавляет сетевые задержки.
Попробуем сравнить:
Без
-c
:rsync
делает быстрый системный вызовstat()
для каждого файла, чтобы получить его размер и дату. Это операция с метаданными, она очень быстрая.С
-c
:rsync
читает всё содержимое каждого файла. Для файла размером 1 ГБ это означает необходимость прочитать 1 ГБ данных только для того, чтобы решить, нужно ли его читать для передачи.
Когда использовать --checksum?
Из-за огромных накладных расходов флаг -c
следует использовать только в особых случаях:
Для критически важных данных, где вероятность ошибки неприемлема (например, бэкап базы данных).
Когда есть подозрение, что файлы могли измениться без обновления mtime.
При синхронизации с файловыми системами или системами, которые ненадёжно работают с временными метками (например, некоторые сетевые ФС, FAT32).
Для проверки целостности уже синхронизированных данных.
Пример команды для гарантированной идентичности:
rsync -avc --progress /source/ user@host:/destination/
Практический совет: Начинайте всегда с обычной синхронизации (rsync -av
). Если вы заметили, что какие-то файлы не обновляются, хотя должны бы, тогда запустите ту же команду с флагом -c
, чтобы принудительно проверить и переслать всё необходимое. Используйте --checksum
выборочно, а не по умолчанию.
3.4. Сжатие vs шифрование: когда -z не помогает, а вредит
Общее правило «использовать -z
для передачи по сети» верно, но оно имеет критически важные исключения. Главный подводный камень - взаимодействие сжатия и шифрования.
Как это работает (упрощённо):
rsync -z
(сжатие): данные сжимаются "на лету" перед отправкой в сеть.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 оправдана значительной экономией времени.
Практическое правило
Тип данных |
Использовать |
Почему? |
---|---|---|
Текст, код, логи, XML/JSON |
Да, обязательно |
Высокий коэффициент сжатия. Большая экономия времени. |
Фотографии (JPG, PNG), видео (MP4, AVI), музыка (MP3) |
Нет |
Файлы уже сжаты. Дополнительное сжатие бесполезно. |
Архивы (ZIP, GZ, RAR) |
Нет |
Файлы уже сжаты. Дополнительное сжатие бесполезно. |
Бинарники, исполняемые файлы |
Скорее нет |
Обычно плохо сжимаются. Лучше проверить на конкретном наборе. |
Смешанное содержимое |
Зависит от ситуации |
Если большая часть - текст, то |
Вывод: не стоит использовать -z
вслепую для всего подряд. Важно учитывать "природу" передаваемых данных. Слепо применять -z
для бэкапа, содержащего много медиа и архивов - верный способ создать лишнюю нагрузку на систему без даже малейшей выгоды.
3.5. Мониторинг производительности: ищем «узкое место»
Когда rsync
работает медленно, причина может быть в разных компонентах системы: процессор, диски или сеть. Правильный мониторинг покажет, на что обратить внимание для оптимизации.
1. Мониторинг процессора (top, htop)
top # или htop (является более наглядной утилитой)
На что обратить внимание?
%Cpu(s): us
(user): показатель загрузки CPU пользовательскими процессами. Высокое значение говорит о том, чтоrsync
(или другие процессы) активно используют процессор.%Cpu(s): sy
(system): показатель загрузки CPU системными вызовами. Может быть высоким, еслиrsync
заставляет ядро работать интенсивно (например, при обработке метаданных миллионов файлов).%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 Мбит/с), а данные хорошо сжимаются (тексты, логи, код), то сжатие позволит уменьшить объем передаваемых данных и, следовательно, повысит эффективность использования медленного канала, ускорив передачу.
Сводная таблица диагностики и решений
Симптом (что показывают утилиты) |
Вероятная причина |
Возможное решение |
---|---|---|
|
Проблема IOPS: слишком много мелких файлов. Диск "прыгает" между ними. |
Исключить лишние файлы ( |
|
Диск не успевает читать/записывать большие файлы. |
Сложно решить. Возможно, другие процессы грузят диск. Запустить |
|
Узкое место - CPU: процессор не успевает шифровать ( |
Убрать |
|
Узкое место - сеть: данные передаются медленно из-за лимита канала или потерь. |
Использовать |
|
Идеальная ситуация: |
Ничего не делать, ждать завершения. |
Подведу итог. Не гадайте, почему rsync
медленный. Откройте три терминала, запустите в них htop
, sudo iotop -o
и sudo nethogs
и Вы сразу увидите слабое звено в системе. Это знание подскажет, какие опции rsync
можно применить для ускорения работы.
Заключение
Полученных в этих статьях знаний - достаточно для подавляющего количества повседневных задач. Но если rsync
перестанет покрывать Ваши возможности - например, Вам потребуется сквозное шифрование, глобальная дедупликация или более сложное управление версиями бэкапов - стоит присмотреться к аналогам: BorgBackup, Restic, Rclone. О них я расскажу в следующей, заключительной статье (кратко, как логичное завершение серии).
P.S. В моей группе в Телеграмм разбираем практические кейсы: скрипты (Python/Bash/PowerShell), тонкости ОС и инструменты для эффективной работы.
Комментарии (0)
ilyailyailya
19.09.2025 17:06В некоторых блоках code, в том числе и внутри параграфов некорректно указанны коды HTML вместо специальных символов (
&
-->&
и т. п.). На таких примерах ничего хорошего не получится.
Looka
С точность до наоборот, поправьте
eternaladm Автор
Спасибо за комментарий! Верно подмечено, оговорился. Спасибо, исправлено!