<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>快照 · XB 现场笔记</title><link>https://hk.xbcatm.com/zh/tags/%E5%BF%AB%E7%85%A7/</link><description>基于 HTTP、DNS、TLS、Caddy、SSH、发布与恢复的可复现运维现场笔记。</description><generator>Hugo</generator><language>zh-CN</language><atom:link href="https://hk.xbcatm.com/zh/tags/%E5%BF%AB%E7%85%A7/index.xml" rel="self" type="application/rss+xml"/><item><title>LVM 快照在时间上定格一块块设备</title><link>https://hk.xbcatm.com/zh/posts/lvm-logical-volume-snapshot/</link><pubDate>Mon, 10 Apr 2028 00:00:00 +0000</pubDate><guid>https://hk.xbcatm.com/zh/posts/lvm-logical-volume-snapshot/</guid><description>经典 LVM 快照在有限 COW 存储中保留变化前的 chunk；一致备份与合并可用性需要分别规划。</description><content:encoded>&lt;p&gt;经典 LVM 快照是在源逻辑卷上附加写时复制异常存储。&lt;code&gt;lvcreate -s -L 5G -n snap vg/data&lt;/code&gt; 请求五 GiB 存储，实际分配还受卷组空闲容量和分配粒度约束。创建时不需要复制整份源卷，但不代表完全没有工作或必然瞬时完成。thin 快照使用另一种共享池机制，不能把经典快照的独立异常存储套到所有 LVM 快照上。&lt;/p&gt;
&lt;p&gt;异常存储要覆盖整个保留窗口内发生变化的不同 chunk 及其元数据，包括备份复制期间的写入。突发负载可能比安静时估算更早耗尽容量。经典存储满后快照会失效，读取开始返回错误。应提前观察有效状态和增长趋势，留下扩容或结束备份的时间；只规定一个百分比告警，而没有响应能力与空闲空间，不能形成可靠恢复计划。&lt;/p&gt;
&lt;h2 id="异常存储不是副本"&gt;异常存储，不是副本&lt;/h2&gt;
&lt;p&gt;源使用 &lt;code&gt;snapshot-origin&lt;/code&gt; 目标，&lt;code&gt;snapshot&lt;/code&gt; 则把未变化的源 chunk 与保存的异常组合成视图。直接写快照的数据进入它自己的 COW 设备。对已保存异常的同一 chunk 反复修改，不需要为这份快照再保存一次原始内容。原文一百万次写入、十六个热块却只有十五个异常的数字，没有找回轨迹；若十六个不同 chunk 都写过，就需十六份数据异常，另加元数据。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;lvs -a -o lv_name,lv_attr,origin,data_percent,lv_size&lt;/code&gt; 展示类型、状态和用量。&lt;code&gt;swi-a-s---&lt;/code&gt; 只是示意，不是所有快照固定的属性串；第五位 I 表示无效快照。告警应覆盖实际增长速率和处理时间。&lt;code&gt;lvextend&lt;/code&gt; 需要 VG 中有可分配空间；&lt;code&gt;lvchange --monitor y&lt;/code&gt; 只启用监控，自动扩容还依赖 dmeventd、有效扩容策略及足够容量，并不是一个开关就能保证成功。&lt;/p&gt;
&lt;h2 id="耗尽即失效"&gt;耗尽即失效&lt;/h2&gt;
&lt;p&gt;内核文档说明，经典 COW 存储耗尽后会禁用快照并返回错误。lvcreate 手册中按源容量百分之二十创建的命令只是例子，不是通用容量建议。规划要包含 chunk 粒度、元数据、突发变化及完整备份时长。在耗尽之前扩容才有助于维持有效快照；恢复点已经失效时，不能因为 LV 名字仍在，就把它当作内容完整的历史状态。&lt;/p&gt;
&lt;p&gt;经典快照溢出时源卷写入通常继续，但这不保证源卷或底层设备不会同时出现其他问题。无效快照不能再作为可用恢复点，却不等于它从未存在；此前已经完成并验证的独立备份可能仍有效。当前快照是否可读，与另一处已完成副本是否完整，应分别核对。不能用夸张的比喻替代这两个不同状态，也不能以源服务还在运行推断快照健康。&lt;/p&gt;
&lt;h2 id="合并回滚以及-thin-如何改变失败形态"&gt;合并回滚，以及 thin 如何改变失败形态&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;lvconvert --merge vg/snap&lt;/code&gt; 将持久化 COW 快照合并回源，并消耗该快照。源与快照两个 LV 都未打开时立即开始，否则延后至二者关闭时的激活机会。合并开始后源可以继续服务读写，必要的受影响写入会等待对应 chunk 完成。这里不保证回滚全程无需中断，而且源上较新的数据可能被丢弃；停用应用、关闭使用者和确认恢复目标都需要明确安排。&lt;/p&gt;
&lt;p&gt;thin 快照与其他 thin LV 共用池的数据及元数据容量，不拥有单独定额的经典 COW 存储。数据空间满时，依策略可能先排队写入，也可能立即返回错误；元数据耗尽又是另一种失败。问题可以影响多个卷及文件系统，却不等于每个卷瞬间全毁。必须同时监控池的两种百分比、满池行为与可扩容能力，不能只观察某一份快照的虚拟大小。&lt;/p&gt;
&lt;h2 id="坏掉的样子"&gt;坏掉的样子&lt;/h2&gt;
&lt;p&gt;块级快照不会自动成为应用一致的备份。文件系统与数据库缓冲、日志，以及跨卷存放的数据，都需要协调并在还原时正确恢复。复制过程中要检查快照有效性、传播读取错误，之后验证内容与还原结果。命令成功退出或挂载成功都不能证明备份完整；本轮没有进行 LVM 创建、耗尽、扩容或合并实验，也没有用一个内存模型替代这些系统验证。&lt;/p&gt;
&lt;p&gt;安排合并前，应识别源与快照双方的打开者，规划应用停机或激活边界。先在一次性卷上演练，并另留独立备份。容量要覆盖整个保留窗口的不同块变化，不能只按其中最忙一小时计算。完成后还需确认实际卷及应用状态，不能仅凭分配计数下降宣布恢复成功。原来的设备名也必须绑定到测试目标，不能直接借用生产卷路径。&lt;/p&gt;
&lt;figure class="technical-diagram"&gt;
&lt;img loading="lazy" src="https://hk.xbcatm.com/visuals/posts-lvm-logical-volume-snapshot.detail.zh.svg" alt="经典 LVM 组合未改源块与有限异常存储，并区分耗尽失效、thin 池容量及合并条件。" width="560" height="1818"&gt;
&lt;figcaption&gt;LVM 保留变化前的 chunk：机制图。&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="快速验证"&gt;快速验证&lt;/h2&gt;
&lt;p&gt;以下原命令保持不变并暂缓执行。名称依赖具体环境，也需要已授权的 LVM 访问；其中 &lt;code&gt;lvextend&lt;/code&gt; 会改变分配，因此整段不是只读诊断。本轮未执行 device-mapper、内核、逻辑卷或挂载操作，不能把这些命令列在文章里就说验证已完成：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;lvs -a -o lv_name,vg_name,lv_attr,origin,data_percent,lv_size
lvs -a -o lv_name,data_percent,metadata_percent vg/thinpool
dmsetup status vg-snap
lvextend -L +2G vg/snap
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;经典快照的 Data% 与有效状态，不同于 thin 池的 Data% 和 Meta%。&lt;code&gt;dmsetup status&lt;/code&gt; 格式取决于目标类型：普通快照分配不必下降，而 snapshot-merge 可向仅剩元数据的状态递减。最后一行既不证明 VG 有空闲容量，也不能使无效快照复活。完整实验需要绑定目标并逐步验证状态变化，相关可执行修改单独保留为暂缓提案。&lt;/p&gt;
&lt;h2 id="来源"&gt;来源&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://man7.org/linux/man-pages/man8/lvcreate.8.html"&gt;lvcreate(8)&lt;/a&gt;，说明 &lt;code&gt;-s&lt;/code&gt;/&lt;code&gt;--snapshot&lt;/code&gt;、COW 与 thin 快照的区别以及容量命令示例，已核验 2026-09-16。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://man7.org/linux/man-pages/man8/lvs.8.html"&gt;lvs(8)&lt;/a&gt;，定义 &lt;code&gt;lv_attr&lt;/code&gt; 状态位，含无效快照，已核验 2026-09-16。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.kernel.org/admin-guide/device-mapper/snapshot.html"&gt;Device-mapper snapshot 支持&lt;/a&gt;，异常存储、失效语义与 &lt;code&gt;lvconvert --merge&lt;/code&gt; 行为，已核验 2026-09-16。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://man7.org/linux/man-pages/man7/lvmthin.7.html"&gt;lvmthin(7)&lt;/a&gt;，thin 快照、池耗尽与自动扩容，已核验 2026-09-16。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://man7.org/linux/man-pages/man8/lvconvert.8.html"&gt;lvconvert(8)&lt;/a&gt;，立即与延后合并以及卷打开状态条件，已核验 2026-10-18。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.postgresql.org/docs/18/backup-file.html"&gt;PostgreSQL filesystem backup&lt;/a&gt;，一致快照范围、WAL 与多卷恢复要求，已核验 2026-10-18。&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>ZFS 快照给文件系统一个回退键</title><link>https://hk.xbcatm.com/zh/posts/zfs-snapshots-multiversion-safety/</link><pubDate>Tue, 21 Mar 2028 00:00:00 +0000</pubDate><guid>https://hk.xbcatm.com/zh/posts/zfs-snapshots-multiversion-safety/</guid><description>ZFS 快照在同一池内保留文件系统视图；应用一致性、保留空间及破坏性回滚需要分别核对。</description><content:encoded>&lt;p&gt;ZFS 快照通过写时复制引用保留某一时点的数据集视图，不需要把全部数据另复制一遍。只要还有引用需要旧块，它们就继续保留。但创建仍涉及元数据与同步工作，不能承诺零分配，也不能把在提示符重画之前返回当作实测保证。本次没有运行 ZFS，保留文件中也未找到原文所称实验的原始输出，所以数值与运行效果必须单独限定来源。&lt;/p&gt;
&lt;p&gt;这样的历史适合本地恢复，但 &lt;code&gt;tank/data&lt;/code&gt; 的快照仍位于同一个池。让池不可用的故障，也可能让全部快照不可用。这描述的是共享故障边界，不是断言每次控制器故障都会原子地毁掉所有块。独立备份或复制还需要自己的保留策略及恢复验证，不能仅因另一处存在相同名字，就宣布已经获得完整灾难恢复能力。&lt;/p&gt;
&lt;h2 id="为一个一致的树命名"&gt;为一个一致的树命名&lt;/h2&gt;
&lt;p&gt;快照原子地捕获数据集的文件系统状态；递归 &lt;code&gt;-r&lt;/code&gt; 会让后代数据集在同一时刻建立快照。不递归时，子数据集被遗漏，并不是自动晚一点另拍一份。文件系统一致不等于应用检查点：数据库缓冲、日志以及选定数据集之外的文件，仍需遵循应用支持的备份或崩溃恢复流程。必须协调所有必要部分，才能把整组状态作为可恢复对象。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;tank/data@daily-2026-09-16&lt;/code&gt; 这样的名字可以辅助保留策略，还应结合创建时间、属性与备份记录；名字本身不是删除资格的证明。&lt;code&gt;zfs list -t snapshot -o name,used,refer,creation -s creation&lt;/code&gt; 用于观察快照及空间统计，真正清理之前还要检查依赖和 hold。自动命名也可以被可靠管理，关键是保留规则有明确、可追溯的依据。&lt;/p&gt;
&lt;h2 id="空间在哪里zfs-list--o-space"&gt;空间在哪里：zfs list -o space&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;zfs list -o space tank/data&lt;/code&gt; 选择 NAME、AVAIL、USED、USEDSNAP、USEDDS、USEDREFRESERV 与 USEDCHILD，不包含 REFER。USED 分解为四项 used-by 类别，不能写成 REFER 加 USEDSNAP。REFER 表示数据集可访问的数据，其中也可能有共享块。单份快照的 USED 统计其独有引用，删除其他快照还会改变这个值，所以逐份相加不等于整体可回收空间。&lt;/p&gt;
&lt;p&gt;原文三十份快照、六十四 MiB 数据集以及每日两 MiB 变化的数字，没有找回对应 ZFS 运行轨迹。六十 MiB 与一千九百二十 MiB 的对比只能当作示意算术，不能作为真实分配结论。元数据、压缩、块尺寸、共享关系及确切保留时间线都会影响成本。新快照独占很少，并不代表整组历史没有阻止大量空间回收。&lt;/p&gt;
&lt;h2 id="holddiff-与版本对比"&gt;hold、diff 与版本对比&lt;/h2&gt;
&lt;p&gt;用户 hold 会阻止销毁，直到明确释放。&lt;code&gt;zfs hold keep tank/data@daily-2026-09-16&lt;/code&gt; 设置一个标签；&lt;code&gt;zfs holds tank/data@daily-2026-09-16&lt;/code&gt; 检查它，&lt;code&gt;zfs release keep tank/data@daily-2026-09-16&lt;/code&gt; 释放该标签。hold 可保护传输中的快照，但克隆源依赖是另一种关系，不一定显示成具名 hold 标签。释放前要弄清依赖的任务，而不是看到忙就盲目取消保护。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;zfs diff tank/data@daily-2026-09-15 tank/data@daily-2026-09-16&lt;/code&gt; 比较仍保留的两个版本，报告新建、删除、修改与重命名路径。文件系统快照会按需挂载到 &lt;code&gt;.zfs/snapshot/&amp;lt;name&amp;gt;&lt;/code&gt; 之下，&lt;code&gt;snapdir=visible&lt;/code&gt; 让目录出现在列表里。读取旧文件与破坏性回滚整个数据集是不同恢复选择，两者本身都没有证明应用业务状态正确。&lt;/p&gt;
&lt;h2 id="坏掉的样子"&gt;坏掉的样子&lt;/h2&gt;
&lt;p&gt;成功发送并接收到独立保护的存储，才可能在源池丢失后保留数据。所示增量传输要求目标已存在，而且最近快照匹配发送基线；只有增量流不构成完整独立备份。同机第二个池仍可能共用电源、控制器或管理员权限，因此不能只数池的个数判断隔离。必须核对接收内容并演练可用还原，才能说明它抵御了哪些故障。&lt;/p&gt;
&lt;p&gt;USEDSNAP 上升应触发容量与保留检查，但池占用超过百分之八十不是统一的写失败阈值，还要观察配额、预留及实际可用空间。克隆依赖源快照，删除源或回滚越过较新依赖时需要有意识地处理关系。回滚会丢弃较新数据，而销毁克隆并非唯一恢复策略。可以选择恢复文件或另建恢复数据集，不应把破坏性递归操作当作自动下一步。&lt;/p&gt;
&lt;figure class="technical-diagram"&gt;
&lt;img loading="lazy" src="https://hk.xbcatm.com/visuals/posts-zfs-snapshots-multiversion-safety.detail.zh.svg" alt="ZFS 快照谱系、克隆依赖、用户 hold 与 USED 统计分开说明，并保留回滚及独立接收的边界。" width="560" height="1828"&gt;
&lt;figcaption&gt;ZFS 历史存在依赖：机制图。&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="快速验证"&gt;快速验证&lt;/h2&gt;
&lt;p&gt;以下独立运行的 Bash 配方尚未执行。它要求独占控制的一次性数据集、已存在的源快照，以及此前已从基线完整接收的目标。目标自基线以来必须未被修改，并挂载在绑定路径；路径中不能有中间挂载或符号链接，所用 hold 标签必须尚未占用。创建目标时点的源快照前，先在源数据集中准备普通文件 &lt;code&gt;probe.txt&lt;/code&gt;，并把可信的外部预期副本放到 &lt;code&gt;expected&lt;/code&gt; 路径。列表递归检查快照，但传输只覆盖 &lt;code&gt;tank/data&lt;/code&gt; 本身，不包含后代数据集；不能把递归列出误当成递归复制。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;set&lt;/span&gt; -euo pipefail
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;tank/data&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;dst&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;backup/data&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;base&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;daily-2026-09-15&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;next&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;daily-2026-09-16&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;tag&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;example-transfer&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;mount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/mnt/zfs-lab/received&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;expected&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/mnt/zfs-lab/expected-v2.txt&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; zfs list -r -t snapshot -o name,used,refer,creation -s creation &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$src&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$dst&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; zfs list -o name,avail,used,usedsnap,usedds,usedrefreserv,usedchild,refer &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$src&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; zfs holds &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$src&lt;/span&gt;&lt;span class="s2"&gt;@&lt;/span&gt;&lt;span class="nv"&gt;$base&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$src&lt;/span&gt;&lt;span class="s2"&gt;@&lt;/span&gt;&lt;span class="nv"&gt;$next&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; zfs diff &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$src&lt;/span&gt;&lt;span class="s2"&gt;@&lt;/span&gt;&lt;span class="nv"&gt;$base&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$src&lt;/span&gt;&lt;span class="s2"&gt;@&lt;/span&gt;&lt;span class="nv"&gt;$next&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;latest&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;zfs list -H -r -t snapshot -o name -s createtxg &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$dst&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; awk -F@ -v &lt;span class="nv"&gt;dataset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$dst&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;$1 == dataset { last=$0 } END { print last }&amp;#39;&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$latest&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$dst&lt;/span&gt;&lt;span class="s2"&gt;@&lt;/span&gt;&lt;span class="nv"&gt;$base&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;source_guid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;zfs get -Hp -o value guid &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$src&lt;/span&gt;&lt;span class="s2"&gt;@&lt;/span&gt;&lt;span class="nv"&gt;$base&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;target_guid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;zfs get -Hp -o value guid &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$dst&lt;/span&gt;&lt;span class="s2"&gt;@&lt;/span&gt;&lt;span class="nv"&gt;$base&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;test&lt;/span&gt; -n &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$source_guid&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$source_guid&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$target_guid&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;target_mount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;zfs get -H -o value mountpoint &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$dst&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;target_mounted&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;zfs get -H -o value mounted &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$dst&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$target_mount&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$mount&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$target_mounted&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; yes
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;test&lt;/span&gt; -f &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$expected&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;test&lt;/span&gt; ! -L &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$expected&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; zfs hold &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$tag&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$src&lt;/span&gt;&lt;span class="s2"&gt;@&lt;/span&gt;&lt;span class="nv"&gt;$base&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$src&lt;/span&gt;&lt;span class="s2"&gt;@&lt;/span&gt;&lt;span class="nv"&gt;$next&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; zfs send -i &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$src&lt;/span&gt;&lt;span class="s2"&gt;@&lt;/span&gt;&lt;span class="nv"&gt;$base&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$src&lt;/span&gt;&lt;span class="s2"&gt;@&lt;/span&gt;&lt;span class="nv"&gt;$next&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; zfs receive &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$dst&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;source_guid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;zfs get -Hp -o value guid &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$src&lt;/span&gt;&lt;span class="s2"&gt;@&lt;/span&gt;&lt;span class="nv"&gt;$next&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;target_guid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;zfs get -Hp -o value guid &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$dst&lt;/span&gt;&lt;span class="s2"&gt;@&lt;/span&gt;&lt;span class="nv"&gt;$next&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;test&lt;/span&gt; -n &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$source_guid&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$source_guid&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$target_guid&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;test&lt;/span&gt; -f &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$mount&lt;/span&gt;&lt;span class="s2"&gt;/.zfs/snapshot/&lt;/span&gt;&lt;span class="nv"&gt;$next&lt;/span&gt;&lt;span class="s2"&gt;/probe.txt&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;test&lt;/span&gt; ! -L &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$mount&lt;/span&gt;&lt;span class="s2"&gt;/.zfs/snapshot/&lt;/span&gt;&lt;span class="nv"&gt;$next&lt;/span&gt;&lt;span class="s2"&gt;/probe.txt&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; cmp -- &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$expected&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$mount&lt;/span&gt;&lt;span class="s2"&gt;/.zfs/snapshot/&lt;/span&gt;&lt;span class="nv"&gt;$next&lt;/span&gt;&lt;span class="s2"&gt;/probe.txt&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;目标最近快照及基线 GUID 必须匹配，仅有相同名字不够。断言失败会停止子 shell，&lt;code&gt;pipefail&lt;/code&gt; 会暴露管道任一端的失败。接收后 GUID 和测试文件字节一致，仍不能证明完整应用可还原：必须另行核对完整清单、元数据与应用恢复。无论成功或失败，已创建的 hold 都保留；完成验证与依赖检查后，仅用 &lt;code&gt;zfs release example-transfer tank/data@daily-2026-09-15 tank/data@daily-2026-09-16&lt;/code&gt; 释放本实验标签。示例不包含强制回滚或自动清理，存储运行与完整恢复验证仍暂缓。&lt;/p&gt;
&lt;h2 id="来源"&gt;来源&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://openzfs.github.io/openzfs-docs/man/master/8/zfs-snapshot.8.html"&gt;zfs-snapshot(8)&lt;/a&gt;，快照创建语义与递归快照，已核验 2026-09-16。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://openzfs.github.io/openzfs-docs/man/master/8/zfs-list.8.html"&gt;zfs-list(8)&lt;/a&gt;，&lt;code&gt;space&lt;/code&gt; 列集与独立的 REFER 属性，已核验 2026-09-16。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://openzfs.github.io/openzfs-docs/man/master/8/zfs-hold.8.html"&gt;zfs-hold(8)&lt;/a&gt;，hold、&lt;code&gt;zfs holds&lt;/code&gt; 以及销毁返回的 EBUSY，已核验 2026-09-16。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://openzfs.github.io/openzfs-docs/man/master/8/zfs-send.8.html"&gt;zfs-send(8)&lt;/a&gt;，增量发送流及其基线快照要求，已核验 2026-09-16。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://openzfs.github.io/openzfs-docs/man/master/7/zfsprops.7.html"&gt;zfsprops(7)&lt;/a&gt;，used-by 分解、共享引用、配额与预留，已核验 2026-10-18。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://openzfs.github.io/openzfs-docs/man/master/8/zfs-clone.8.html"&gt;zfs-clone(8)&lt;/a&gt;，克隆对源快照的依赖，已核验 2026-10-18。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://openzfs.github.io/openzfs-docs/man/master/8/zfs-rollback.8.html"&gt;zfs-rollback(8)&lt;/a&gt;，较新数据丢弃及依赖克隆处理，已核验 2026-10-18。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://openzfs.github.io/openzfs-docs/man/master/8/zfs-receive.8.html"&gt;zfs-receive(8)&lt;/a&gt;，增量接收要求匹配目标基线，已核验 2026-10-18。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.postgresql.org/docs/18/backup-file.html"&gt;PostgreSQL filesystem backup&lt;/a&gt;，一致快照范围、WAL 与多卷恢复要求，已核验 2026-10-18。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://openzfs.github.io/openzfs-docs/man/master/7/zfsconcepts.7.html"&gt;ZFS concepts&lt;/a&gt;，克隆对源快照的依赖与共享存储边界，已核验 2026-10-18。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://openzfs.github.io/openzfs-docs/man/master/8/zfs-get.8.html"&gt;zfs-get(8)&lt;/a&gt;，用于 GUID 与挂载点检查的精确属性读取，已核验 2026-10-19。&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>Btrfs 快照是写时复制，不是复制</title><link>https://hk.xbcatm.com/zh/posts/btrfs-snapshots-are-copy-on-write/</link><pubDate>Sun, 14 Nov 2027 00:00:00 +0000</pubDate><guid>https://hk.xbcatm.com/zh/posts/btrfs-snapshots-are-copy-on-write/</guid><description>Btrfs 快照在同一文件系统内共享文件 extent；元数据、变化保留与应用恢复仍需规划。</description><content:encoded>&lt;p&gt;Btrfs 快照是带有源初始内容的新子卷，通过共享已有文件 extent 保留视图，而不是逐块复制全部数据。创建会复制根部元数据，也可能等待源中的脏数据刷出，因此不能承诺无论大小都在毫秒内完成。同一 extent 还可能同时被多个快照或 reflink 引用，不存在固定只能引用两次的规则。共享数据与完全没有创建成本，是不同的判断。&lt;/p&gt;
&lt;p&gt;快照让本地恢复点较为经济，但成本包含元数据以及后续分岔保留下来的旧块。它与源共用整个文件系统的存储池，而文件系统可能跨多块设备，并不一定只在一块磁盘上。快照数量持续增加时，已删除或覆盖的数据仍可能被保留。即使设置为只读，同一文件系统内的快照也不是独立备份，不能把版本数量当作故障隔离程度。&lt;/p&gt;
&lt;h2 id="共享-extent而不是复制数据块"&gt;共享 extent，而不是复制数据块&lt;/h2&gt;
&lt;p&gt;快照是有独立根的子卷。示例 &lt;code&gt;btrfs subvolume snapshot -r &amp;quot;$src&amp;quot; &amp;quot;$snap&amp;quot;&lt;/code&gt; 请求只读快照，普通快照则可以写入。只读标志限制通过这个子卷进行的普通修改，不防管理员删除或底层损坏。快照不是递归操作：嵌套子卷在其中只是空的占位目录，其内部文件不会自动包含进来。恢复计划必须另行处理这些子卷，不能只检查顶层目录是否存在。&lt;/p&gt;
&lt;p&gt;共享 extent 被修改时，写时复制为其余引用保留旧内容。实际分配受到 extent 布局、压缩、元数据和后续快照影响，不能简单等同于应用写了多少字节。原文中的 APFS 克隆数字不是 Btrfs 测量，本次也没有找到其原始轨迹；不能用它们证明创建零成本，或同一范围第二次覆盖时 Btrfs 一定不再分配。跨文件系统类比只能解释思路，不能替代本系统验证。&lt;/p&gt;
&lt;h2 id="空间到底去了哪里"&gt;空间到底去了哪里&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;btrfs filesystem du&lt;/code&gt; 通过 FIEMAP 报告文件 extent 的 Total、Exclusive 和 Set shared。每个参数各自定义递归扫描的文件集合，Set shared 统计该集合引用的共享范围并处理重叠，并非只统计与其他命令行参数之间的共享。&lt;code&gt;compsize&lt;/code&gt; 显示面向压缩的 Disk Usage、Uncompressed 与 Referenced，不是相同三列。qgroup 则要求先启用配额并保持统计一致，不能把缺失统计解释成零占用。&lt;/p&gt;
&lt;p&gt;删除源不会释放仍由快照或 reflink 引用的 extent；只有其他引用都消失时，它才可能成为某个快照的独占数据。删除子卷还分为移除目录和后台清理两个阶段，所以空间不一定立即归还。&lt;code&gt;df&lt;/code&gt; 反映文件系统整体容量，不能把每个未释放字节精确归到某一份快照。设计保留周期时，需要同时考虑共享关系与后台回收，而不是只数目录数量。&lt;/p&gt;
&lt;h2 id="还原点不是副本"&gt;还原点不是副本&lt;/h2&gt;
&lt;p&gt;本地快照可以保留有用的文件系统视图，却不自动保证应用一致性。数据库可能需要协调备份步骤、保留日志，并在还原后执行恢复；应用尚未交给文件系统的缓冲内容不在快照里。源与快照也共享底层故障风险。回滚并非固定时间完成的保证，更不能替代应用启动及内容正确性的验证；文件能列出和业务能恢复，应分别记录。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;btrfs send&lt;/code&gt; 生成供另一个文件系统的 &lt;code&gt;btrfs receive&lt;/code&gt; 使用的数据流。增量 &lt;code&gt;-p&lt;/code&gt; 依赖两端都存在且未被改变的合适父快照，参与发送的所有快照都必须只读。独立存储只能隔离它没有共同承受的故障，还要验证接收成功、保留策略和还原内容。快照频率更高也不保证每次传输都按固定比例缩小，变化模式和参考快照同样影响结果。&lt;/p&gt;
&lt;h2 id="坏掉的样子"&gt;坏掉的样子&lt;/h2&gt;
&lt;p&gt;删除大目录后空间几乎不变，应先检查快照及 reflink 引用，再决定是否继续删除。&lt;code&gt;btrfs filesystem du -s&lt;/code&gt; 与 qgroup 提供不同口径，某一个 Exclusive 值不一定解释全部差额。清理恢复点之前还要核对保留要求和发送依赖。多个快照可能共同保留某些块，即使没有任何一份单独独占它们，所以不能仅按最大独占值猜测可释放容量。&lt;/p&gt;
&lt;p&gt;写时复制需要为数据及元数据保留分配余量。用 &lt;code&gt;btrfs filesystem usage&lt;/code&gt; 分别观察两者；设备尚未分配的空间，与已分配块组内部仍空闲的空间不是同一指标。不能从这些计数推出统一百分之五余量就足够，也不能断言两种空间总按固定顺序耗尽。一次分配失败本身同样不证明文件系统已切成只读，应结合真实错误与状态判断。&lt;/p&gt;
&lt;figure class="technical-diagram"&gt;
&lt;img loading="lazy" src="https://hk.xbcatm.com/visuals/posts-btrfs-snapshots-are-copy-on-write.detail.zh.svg" alt="Btrfs 根共享 extent，分岔保留旧数据；同时说明嵌套子卷遗漏、引用保留及独立恢复条件。" width="560" height="1700"&gt;
&lt;figcaption&gt;Btrfs 共享 extent：机制图。&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="快速验证"&gt;快速验证&lt;/h2&gt;
&lt;p&gt;以下是尚未执行的有限检查。它要求已授权的一次性源子卷 &lt;code&gt;/mnt/btrfs-lab/data&lt;/code&gt;、同一 Btrfs 文件系统中已存在的 &lt;code&gt;snapshots&lt;/code&gt; 父目录，以及所需工具和权限。所有路径组件必须无符号链接，并在整个检查与创建过程中由本实验独占控制。目标若已是目录，Btrfs 会改变实际创建位置；下方检查拒绝已存在的路径，也拒绝悬空符号链接。这个检查不是防竞态的锁，无法保证独占控制时应停止，不能把示例默默套到生产路径。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;set&lt;/span&gt; -eu
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/mnt/btrfs-lab/data&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;snap&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/mnt/btrfs-lab/snapshots/data-2026-09-16&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; -e &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$snap&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; -L &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$snap&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;%s\n&amp;#39;&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Refusing an existing snapshot destination.&amp;#39;&lt;/span&gt; &amp;gt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; btrfs subvolume snapshot -r &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$src&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$snap&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; btrfs filesystem du -s &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$src&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$snap&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; compsize &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$snap&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; btrfs filesystem usage &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$src&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;快照创建与随后的 du、compsize 使用绑定的同一目标；重复运行会拒绝该目标，不会自动删除快照。文件 extent 计数不测量全部快照元数据。Exclusive 等于 Total，只说明当前 extent 不再共享，不能证明该子卷从来不是快照。这不是分岔或还原实验：后续仍须记录已知测试文件的原始字节，确认源与快照起初相同，仅修改源，再确认快照保留旧字节、源包含新字节。相关存储检查及应用恢复仍暂缓，不能把语法通过视为运行通过。&lt;/p&gt;
&lt;h2 id="来源"&gt;来源&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://btrfs.readthedocs.io/en/latest/btrfs-subvolume.html"&gt;btrfs 子卷文档&lt;/a&gt;，子卷与快照语义，含只读快照，已核验 2026-09-16。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://btrfs.readthedocs.io/en/latest/btrfs-filesystem.html"&gt;btrfs filesystem 文档&lt;/a&gt;，定义上文所用的 &lt;code&gt;du&lt;/code&gt; Exclusive 与 Set shared 列，已核验 2026-09-16。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://btrfs.readthedocs.io/en/latest/btrfs-qgroup.html"&gt;btrfs qgroup 文档&lt;/a&gt;，按 qgroup 统计 exclusive 与 referenced 字节，已核验 2026-09-16。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://btrfs.readthedocs.io/en/latest/btrfs-send.html"&gt;btrfs send 文档&lt;/a&gt;，发送流生成与只读增量基线要求，已核验 2026-09-16。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/kilobyte/compsize/blob/master/compsize.8"&gt;compsize upstream manual&lt;/a&gt;，压缩与引用尺寸统计不同于 filesystem du，已核验 2026-10-18。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.postgresql.org/docs/18/backup-file.html"&gt;PostgreSQL filesystem backup&lt;/a&gt;，一致快照范围、WAL 与多卷恢复要求，已核验 2026-10-18。&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item></channel></rss>