Инкрементальные резервные копии Btrfs send/receive: как строить цепочки снапшотов без скрытых рисков | AdminWiki

Инкрементальные резервные копии Btrfs send/receive: как строить цепочки снапшотов без скрытых рисков

20 сентября 2026 15 мин. чтения

Инкрементальная копия в Btrfs строится на паре read-only снапшотов: команда btrfs send -p передаёт в поток только изменения относительно указанного предка, а btrfs receive на приёмнике применяет этот поток и создаёт снапшот с тем же содержимым. Родительский снапшот (parent snapshot) должен существовать на приёмнике в момент применения, иначе приём завершится ошибкой.

Отсюда три условия, которые определяют всю схему. Снапшоты создаются только с ключом -r, приём ведётся на Btrfs-файловую систему, а удаление старых копий планируется так, чтобы у следующего инкремента оставался доступный предок. Нарушение любого из условий не портит уже принятые снапшоты, но ломает саму возможность передавать инкременты, и тогда восстановление схемы сводится к полной пересылке данных.

Дальше: механика parent snapshot и полей UUID, готовые команды для локальных и удалённых копий, проверка целостности через scrub и check, ротация без разрыва цепочки, разбор ошибок receive и сравнение с rsync, ZFS send/receive и дедуплицирующими инструментами. Синтаксис команд приведён в виде, принятом в btrfs-progs; отдельно указано, что следует из документации (man-страницы btrfs-send, btrfs-receive, btrfs-subvolume), а что относится к практическим рекомендациям.

Как работает инкрементальный btrfs send/receive и при чём здесь parent snapshot

btrfs send читает готовый read-only снапшот и выдаёт на stdout поток команд, описывающий его содержимое. Без дополнительных флагов поток описывает снапшот целиком, то есть это полная копия. С флагом -p указывается второй, ранее созданный снапшот, который уже есть на приёмнике, и в поток попадают только различия между ними.

Схема простая: есть снапшот A (предок) и снапшот B (новый). Состояние A уже лежит на приёмнике, поэтому достаточно передать разницу:

btrfs send -p /snapshots/data_2026-09-19 /snapshots/data_2026-09-20 | btrfs receive /backup

Здесь /snapshots/data_2026-09-19 это parent, /snapshots/data_2026-09-20 это новый снапшот. Приёмник применяет разницу к своей копии предка и получает снапшот с данными B.

Требования к паре снапшотов описаны в документации btrfs-send: оба должны быть read-only, оба должны принадлежать одной Btrfs-файловой системе, а parent обязан иметь общую историю снимков с отправляемым снапшотом. Если связь нарушена, send не построит корректную разницу и завершится ошибкой.

Первое копирование всегда полное: parent отсутствует, потому что на приёмнике ещё нет ни одного звена этой цепочки. Каждый следующий инкремент опирается на предыдущий.

На приёмнике снапшот получает собственный новый UUID, а исходный UUID снапшота-источника записывается в поле Received UUID. По этому полю receive и определяет, какой из локальных снапшотов годится в качестве предка для входящего потока. Для каждого снапшота в цепочке можно проверить, что структура данных сохранена корректно, в том числе на уровне subvolume.

Что такое parent snapshot и как его выбрать

Parent это предыдущий снапшот в цепочке, от состояния которого отсчитываются изменения. Ошибка в выборе даёт один из двух исходов: send передаёт лишние данные, если предок выбран слишком далеко назад, или отказывается работать, если указан снапшот из другой ветки.

Кандидатов удобно смотреть таблицей. Команда btrfs subvolume list с ключами -t (табличный вывод), -u (UUID), -q (parent UUID) и -R (received UUID) показывает сразу всё, что нужно для сопоставления:

btrfs subvolume list -t -u -q -R /backup

Детали по конкретному снапшоту выводит btrfs subvolume show:

btrfs subvolume show /backup/data_2026-09-20
ПолеЧто означаетЗачем нужно
UUIDСобственный идентификатор снапшота на этой файловой системеОднозначно называет снапшот на приёмнике
Parent UUIDСсылка на снапшот-предок, если он известен системеПоказывает место снапшота в цепочке
Received UUIDUUID снапшота-источника, из которого построена эта копияСвязывает приёмник с источником и определяет, годится ли снапшот в parent

Практический приём, который экономит время при разборе инцидентов: вести журнал снапшотов. Минимально достаточно строки из имени снапшота, его UUID на источнике, даты создания и признака, был ли он уже отправлен. Без журнала после удаления пары старых копий восстановить порядок цепочки по одним именам сложно.

Parent должен быть доступен на обеих сторонах в момент отправки. Если снапшот на источнике удалён, инкремент от него отправить уже нельзя, даже когда копия на приёмнике осталась.

Почему снапшоты должны быть read-only

Документация btrfs-send требует, чтобы отправляемый subvolume был доступен только для чтения. Изменяемый снапшот send не обработает. Причина в том, что поток описывает фиксированное состояние: если по дереву в это время идёт запись, разница между родителем и потомком перестаёт быть определённой.

Создание всегда выполняется с ключом -r:

btrfs subvolume snapshot -r /data /snapshots/data_2026-09-20

Это требование документации. Практическая часть другая: имена снапшотов формируют с датой и временем, чтобы приёмник не столкнулся с уже существующим именем.

btrfs receive создаёт принятый снапшот тоже read-only. Снять флаг можно командой btrfs property set /backup/data_2026-09-20 ro false, но использовать такой снапшот дальше как parent рискованно: он перестаёт быть точной копией источника, и различия, которые посчитает send, будут включать правки, сделанные уже на приёмнике.

Пошаговая настройка локальных инкрементальных копий

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

  1. Создать первый read-only снапшот.
    btrfs subvolume snapshot -r /data /snapshots/data_2026-09-19
  2. Отправить его на приёмник полным потоком.
    btrfs send /snapshots/data_2026-09-19 | btrfs receive /backup
  3. Создать второй снапшот.
    btrfs subvolume snapshot -r /data /snapshots/data_2026-09-20
  4. Отправить инкремент от первого снапшота ко второму.
    btrfs send -p /snapshots/data_2026-09-19 /snapshots/data_2026-09-20 | btrfs receive /backup

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

Если на приёмнике уже есть subvolume с тем же именем, receive завершится ошибкой. Отсюда правило именования с точностью до минут, например data_2026-09-20T0315.

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

set -o pipefail
btrfs send -p /snapshots/data_2026-09-19 /snapshots/data_2026-09-20 | btrfs receive /backup

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

btrfs send -p /snapshots/data_2026-09-19 /snapshots/data_2026-09-20 > /mnt/usb/data_inc_2026-09-20.btrfs
btrfs receive /backup < /mnt/usb/data_inc_2026-09-20.btrfs

Файл потока можно хранить на любой файловой системе, включая ext4 или сетевую папку: для receive важна только принимающая сторона.

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

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

df -h /backup
btrfs filesystem du -s /snapshots/data_2026-09-19

btrfs filesystem du показывает занятое место с учётом общих экстентов, поэтому по нему видно, сколько данных реально уйдёт по проводу.

Сжатие на лету снижает трафик, но нагружает процессор на обеих сторонах. Классический вариант через gzip:

btrfs send /snapshots/data_2026-09-19 | gzip -1 | ssh backup@host 'gunzip | btrfs receive /backup'

Уровень -1 даёт разумный компромисс: заголовки и текстовые данные сжимаются заметно, а процессор не становится узким местом. Для уже сжатых данных (архивы, медиа, образы дисков) выигрыш близок к нулю, и тогда сжатие только тратит ресурсы.

Практический ориентир: сжатие включают, когда канал медленнее 1 Гбит/с или данные преимущественно текстовые. В локальной сети 10 Гбит/с быстрее отправить поток без обработки.

Удалённые инкрементальные копии через SSH: команды и нюансы

Базовая команда отличается от локальной только тем, что правая часть конвейера выполняется на удалённой машине:

btrfs send -p /snapshots/data_2026-09-19 /snapshots/data_2026-09-20 | ssh backup@host 'btrfs receive /backup'

На приёмнике нужны btrfs-progs и смонтированная Btrfs-файловая система. Пользователь, от имени которого работает receive, должен иметь право создавать subvolume в целевом каталоге: на практике это root либо отдельная учётная запись с узким sudo, разрешающим только btrfs receive.

Сжатие можно поручить самому SSH:

btrfs send -p /snapshots/data_2026-09-19 /snapshots/data_2026-09-20 | ssh -C backup@host 'btrfs receive /backup'

Флаг -C включает сжатие SSH. Внешний gzip или zstd обычно даёт лучший коэффициент, чем встроенное сжатие SSH, но требует установленного архиватора на обеих сторонах.

Обрыв соединения это основной риск удалённой схемы. Если SSH падает в середине потока, receive на удалённой стороне прекращает работу. В большинстве случаев незавершённый subvolume удаляется самим receive, однако при жёстком прерывании процесса или потере питания на приёмнике может остаться частично принятый снапшот. Перед повтором его нужно удалить:

btrfs subvolume list -t /backup
btrfs subvolume delete /backup/data_2026-09-20

Удалять можно только тот subvolume, который действительно неполный. Имя сверяют со списком и с журналом снапшотов.

Долгие передачи запускают в screen или tmux, чтобы разрыв клиентской сессии не убивал процесс на источнике.

Как обеспечить безопасность и автоматизацию

Для работы по расписанию нужен вход без пароля. Ключ без парольной фразы на бэкап-сервере стоит ограничить: в authorized_keys полезно указать restrict, no-pty и команду через command=. Если receive выполняется от root, отдельный ключ с ограниченной областью применения снижает последствия компрометации источника.

Практическая схема: отдельный пользователь backup с домашним каталогом и доступом только к точке монтирования /backup; ключ источника прописан именно этому пользователю; sudo разрешает лишь btrfs receive и команды проверки. Полный root по SSH для регулярных копий не нужен.

Перед применением потока можно посмотреть, что в нём, не записывая данные на диск. Ключ btrfs receive --dump разбирает поток и печатает операции:

btrfs send -p /snapshots/data_2026-09-19 /snapshots/data_2026-09-20 | btrfs receive --dump | head -50

Это полезно при разборе спорных случаев: видно, какие файлы меняются и какие клоны создаются. Для регулярных копий такой просмотр избыточен, потому что удваивает время обработки.

Автоматизацию строят как обёртку вокруг пары команд: скрипт создаёт снапшот, отправляет инкремент, проверяет коды возврата обоих процессов, пишет результат в лог и удаляет снапшот только после успешного приёма. Политику хранения и проверку восстановления удобно собирать по общей схеме из материала о резервном копировании по правилу 3-2-1 со связками rsync, ZFS, Restic и BorgBackup.

Проверка целостности: как убедиться, что инкремент применился корректно

Важное ограничение: btrfs send/receive сам по себе не подтверждает, что данные на источнике читались без ошибок. Поток снабжён контрольными суммами на уровне служебных команд, поэтому обрыв или порча передачи на приёмнике выявляется, но скрытое повреждение, возникшее на источнике до отправки, попадёт в копию молча. Это практический вывод из устройства потока, а не гарантия, которую даёт документация; проверять источник отдельно всё равно нужно.

Минимальный набор проверок после каждого инкремента:

  1. Убедиться, что receive завершился с кодом 0.
  2. Сравнить UUID снапшота на источнике с полем Received UUID на приёмнике.
  3. Проверить свободное место: снапшоты делят экстенты, и нехватка места ломает следующие операции.

Сравнение идентификаторов:

btrfs subvolume show /snapshots/data_2026-09-20 | grep -i uuid
btrfs subvolume show /backup/data_2026-09-20 | grep -i uuid

UUID источника из первой команды должен совпасть с Received UUID на приёмнике. Если значения разошлись, принят не тот снапшот или цепочка собрана неверно.

Контроль свободного места:

btrfs filesystem usage /backup

Использование btrfs scrub и btrfs check

btrfs scrub проверяет контрольные суммы блоков данных и метаданных на смонтированной файловой системе. Он работает онлайн, читает диски и при наличии второй копии блока на RAID-профиле может исправить повреждение. Запуск и контроль статуса:

btrfs scrub start /backup
btrfs scrub status /backup

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

btrfs check это офлайн-инструмент для проверки и восстановления структур файловой системы. В современных версиях btrfs-progs он по умолчанию только читает и ничего не меняет; запись выполняет единственный ключ --repair. Запускают проверку на размонтированной файловой системе:

umount /backup
btrfs check /dev/sdX

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

Разделение простое: scrub отвечает на вопрос, все ли блоки читаются и совпадают ли контрольные суммы, а check показывает, целостны ли сами структуры Btrfs. Для регулярной эксплуатации достаточно scrub; check нужен при подозрении на повреждение дерева или после сбоя питания.

Ротация снапшотов: как удалять старые копии, не разрывая цепочку

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

Цепочка нужна для передачи. Чтобы применить следующий инкремент, receive должен найти на приёмнике снапшот с тем же Received UUID, что и указанный parent. Если такой снапшот удалён, инкремент не применится, и придётся снова отправлять полную копию. То же ограничение действует на источнике: после удаления снапшота-предка отправить от него инкремент уже нельзя.

Отсюда правило ротации: держать на обеих сторонах скользящее окно свежих снапшотов, которые используются как parent, и отдельно хранить долгосрочные копии, которые в роли parent не нужны.

Перед удалением проверяют, не служит ли снапшот предком для более новых. Таблица с UUID и Received UUID показывает связи сразу:

btrfs subvolume list -t -u -q -R /backup

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

Стратегии ротации: grandfather-father-son и другие

Схема GFS предполагает три уровня хранения: ежедневные копии за последнюю неделю, еженедельные за последний месяц, ежемесячные за год. Для Btrfs это выражается в наборе снапшотов с разными интервалами и разными сроками жизни.

Рабочая упрощённая модель:

  • 7 ежедневных снапшотов, каждый служит parent для следующего.
  • 4 еженедельных, создаваемых от последнего ежедневного в конце недели.
  • 12 ежемесячных как долгосрочные точки восстановления.

Ежемесячный снапшот удобно раз в месяц отправлять полным потоком, начиная новую цепочку. Тогда потеря любого промежуточного звена не требует пересылки всей истории.

Автоматизировать ротацию можно скриптом или готовым инструментом. btrbk строит расписания, выполняет send/receive и удаляет устаревшие копии по политике хранения. snapper ориентирован на локальные снапшоты с алгоритмами очистки по числу и по времени. Оба инструмента задают свои правила удаления, поэтому после первичной настройки нужно посмотреть, какие именно снапшоты остаются, и убедиться, что среди них всегда есть актуальный parent.

Любой собственный скрипт ротации требует теста на копии данных. Ошибка в порядке удаления не уничтожает данные на приёмнике, но превращает следующий инкремент в полную пересылку, а на медленном канале это часы без свежего бэкапа.

Восстановление при разрыве цепочки снапшотов

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

Порядок диагностики:

  1. Посмотреть список снапшотов на приёмнике с UUID и Received UUID.
  2. Сравнить с журналом снапшотов и определить, какое звено отсутствует.
  3. Найти ближайший доступный снапшот, который есть на обеих сторонах и годится в качестве parent.
  4. Если такого снапшота нет, отправить полную копию без -p и продолжить цепочку от неё.
btrfs subvolume list -t -u -q -R /backup
btrfs subvolume show /backup/data_2026-09-19

Команда для возобновления работы после выбора предка:

btrfs send -p /snapshots/data_2026-09-19 /snapshots/data_2026-09-20 | ssh backup@host 'btrfs receive /backup'

Если подходящего предка не осталось, выполняют полную отправку и начинают новую цепочку:

btrfs send /snapshots/data_2026-09-20 | ssh backup@host 'btrfs receive /backup'

Склеивать цепочку вручную, подменяя UUID или переименовывая снапшоты, нельзя. Поля UUID и Received UUID связаны со внутренними структурами Btrfs, и подмена приведёт к тому, что следующий receive либо откажется работать, либо создаст копию с неверной историей, а это обнаружится только при восстановлении.

Страховка от таких ситуаций одна: держать вне цепочки хотя бы один полный бэкап, не зависящий от промежуточных звеньев. Логику откатов и защиты перед рискованными операциями на том же Btrfs разбирает статья про снимки Btrfs для безопасного обновления Linux-сервера.

Что делать, если receive завершился ошибкой

Формулировки сообщений зависят от версии btrfs-progs, но причины и действия повторяются.

Что видно в выводеПричинаЧто делать
cannot find parent subvolumeНа приёмнике нет снапшота с нужным Received UUIDВыбрать доступный parent из цепочки или отправить полную копию
Сообщение о том, что subvolume уже существуетВ целевом каталоге есть снапшот с тем же именемУдалить неполный снапшот или задать другое имя
Поток оборвался, receive прерванРазрыв SSH, kill процесса, перезагрузкаПроверить список subvolume, удалить неполный, повторить передачу
Несовпадение UUID или отказ принять цепочкуПринят снапшот из другой линии или переиспользовано имяПересобрать цепочку от ближайшего общего предка либо начать заново полным потоком

После любой ошибки сначала удаляют неполный subvolume, затем проверяют наличие parent и только потом повторяют передачу. Повторный запуск приёма поверх частично принятого снапшота с тем же именем гарантированно упрётся в конфликт имён.

Скрытые риски и ограничения btrfs send/receive

  1. Версии btrfs-progs и ядра. Поток send имеет версию протокола, и приёмник со старым btrfs-progs может не разобрать поток от нового источника. Перед настройкой сверяйте btrfs --version и uname -r на обеих сторонах и обновляйте пакеты синхронно. Это практическая рекомендация: точные правила совместимости версий нужно сверять с документацией btrfs-progs и ядра Btrfs для конкретных версий, и здесь они не подтверждены.
  2. Нет проверки содержимого из коробки. Контрольные суммы защищают служебные команды потока, но не доказывают корректность данных на источнике. Регулярный scrub закрывает большую часть риска.
  3. Read-only и общая история. Отправлять можно только read-only снапшоты, связанные общей историей с parent. Копия, сделанная утилитой cp или перенесённая между файловыми системами, предком не станет.
  4. Приёмник обязан быть Btrfs. Если целевая система работает на ext4 или XFS, прямой приём невозможен. Поток можно сохранить файлом (btrfs send /snapshots/data_2026-09-20 > /mnt/nas/data.btrfs) и применить позже, но распаковка всё равно потребует Btrfs.
  5. Снапшоты не заменяют резервную копию. Локальные снимки защищают от случайного удаления, пока цепочка существует, но копия на том же узле гибнет вместе с узлом, а ошибка, попавшая в отправленный снапшот, разойдётся по всей цепочке.

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

Сравнение с rsync и ZFS send/receive

ИнструментЧто нужно на приёмникеКак передаёт измененияОграничения
btrfs send/receiveBtrfs и btrfs-progsРазница между двумя read-only снапшотами, с общими экстентамиТребует Btrfs на обоих концах, зависит от наличия parent в цепочке
ZFS send/receiveZFS с пуломРазница между снапшотами, есть режимы -I и -R для цепочек и рекурсииТребует ZFS на обеих сторонах, репликой управляют отдельные инструменты
rsyncЛюбая файловая система, SSHСравнивает файлы по размеру, времени или контрольной суммеНе сохраняет снапшоты и метаданные ФС, медленно на миллионах мелких файлов, нет блочной дедупликации
restic и BorgBackupЛюбая ФС плюс хранилищеИнкремент по блокам с дедупликацией и шифрованиемНе даёт мгновенную снапшот-реплику, обычно копируют выбранные каталоги, а не всю ФС

Выбор диктует инфраструктура. Когда Btrfs стоит на обеих сторонах, send/receive даёт самую дешёвую по трафику реплику. Если приёмник работает на ZFS, логику инкрементов и цепочек удобнее строить по схеме из руководства про настройку репликации данных ZFS с командами zfs send/receive. Для разнородного окружения и облачных хранилищ разумная связка такая: restic или BorgBackup для файловых копий плюс периодический полный поток btrfs send в файл как неподвижная точка восстановления.

Начните с одного тестового набора данных: создайте два read-only снапшота, отправьте полный поток, затем инкремент, сверьте Received UUID на приёмнике и прогоните scrub. Если проверки прошли, схему можно переводить в cron и наращивать политику хранения.

Поделиться:
Сохранить гайд? В закладки браузера