btrfs增量备份必须基于有祖先关系的快照,即后者须由前者(或其子快照)通过btrfs subvolume snapshot -r创建;否则send报“cannot find parent”错误。

send/receive 增量备份必须基于两个有祖先关系的快照
不是任意两个快照都能做增量 btrfs send,必须满足「后者是前者的后代」——也就是后者是用 btrfs subvolume snapshot -r 从前者(或其子快照)创建出来的。否则会报错 ERROR: cannot find parent for ...。
实操建议:
- 每次备份前,用
btrfs subvolume list -t查看快照的Parent UUID字段,确认继承链 - 推荐用命名规范强制可追溯,比如
@snap-2024-06-15-0200→@snap-2024-06-16-0200,且后者一定从前者只读快照而来 - 如果误删了中间快照,
btrfs send -p会直接失败,此时只能退回到全量btrfs send,或用btrfs send -c(需接收端已有相同父快照)
接收端 subvolume 必须不存在,且挂载点需支持 btrfs
btrfs receive 不会覆盖、合并或跳过已存在的子卷,只要目标路径下 @backup 已存在,就会卡住并报 ERROR: cannot create subvolume ... File exists。而且接收目录本身必须是 btrfs 文件系统挂载点——哪怕只是挂载了子卷,也不行。
实操建议:
- 接收前先清理:
btrfs subvolume delete /mnt/backup/@backup(如果存在) - 确保
/mnt/backup是 btrfs 挂载点,而不是某个 btrfs 子卷的挂载;可用findmnt -o SOURCE,TARGET,FSTYPE | grep backup验证 - 别把接收目录设成
/home这类常驻挂载点——万一挂载选项不含subvol=,receive可能写到根子卷,导致混乱
send 的 -p 和 -c 参数区别直接影响恢复灵活性
btrfs send -p 生成的是「相对父快照的增量流」,依赖发送端和接收端都持有那个父快照;btrfs send -c 则指定一个「已存在于接收端的快照」作为参考,允许你跳过中间快照做差量传输(比如从 v1 直接发 v3,只要 v1 在接收端存在)。
实操建议:
- 日常轮转备份建议统一用
-p:逻辑清晰、校验简单,适合脚本自动化 - 网络中断后重传或跨节点同步时,用
-c更省带宽,但得先rsync或scp把父快照元数据同步过去(实际只需subvolume list -t能识别即可) -
-c不等于「任意快照都能当 base」:它要求该快照在接收端的uuid和发送端一致,即必须是同一个子卷的原始快照,不能是 clone 或 rename 后的副本
管道传输时务必加 --no-progress 和 pv 控制节奏
默认 btrfs send 会输出进度行(如 At subvol ...),混在二进制流里会导致 btrfs receive 解析失败,报 ERROR: bad magic value。同时,未经节流的 send/receive 在慢速链路上容易因 buffer 满、超时断连。
实操建议:
- 发送端一定要加
--no-progress:btrfs send --no-progress -p @snap-0615 @snap-0616 | ssh host 'btrfs receive /mnt/backup' - 加
pv -s $(stat -c %s /path/to/snap) |可视化进度,并通过-L限速避免打满带宽 - 别用
gzip管道压缩 send 流:虽然能减体积,但一旦出错无法定位是哪块数据损坏;如真要压缩,应在 send 前对源文件系统做chattr +c启用透明压缩
真正麻烦的不是命令敲错,而是快照之间隐含的父子关系断裂,或者接收端残留了同名但不同源的子卷——这些不会立刻报错,但某天 receive 突然失败,翻日志才发现 UUID 对不上。










