ZFS:存储池、事务组与同步写恢复

本页目录

同步写已经成功,最新 TXG 却还没提交;删除当前文件,空间也没有完全归还。ZFS 中这两个现象分别由恢复依据和共享引用解释,不能只用“有缓存”概括。

要解释的现象阅读位置
四块盘能否承受任意两盘损坏?第 1~2 节:池与 vdev
同步成功后靠什么恢复?第 3 节:TXG 与 ZIL 时间线
SLOG、L2ARC、special vdev 能否互换?第 4 节:设备职责
快照为何保留空间,校验为何不等于备份?第 5~7 节:引用账与恢复能力

1. pool、vdev 与 dataset 的层次

对象职责不能混同的概念
pool汇集分配空间并管理池级状态不等于一个目录
顶层 data vdev承担数据分配与自身冗余多个顶层 vdev 不是自动互为镜像
mirror / RAIDZ vdev组织成员设备的冗余布局冗余容错范围通常先按各 vdev 分析
filesystem dataset文件目录、属性、配额、快照等管理边界不需要单独分区,但共享池资源
zvol暴露块设备的 volume dataset不直接提供普通目录树
snapshot / clone保留历史状态 / 基于快照可写派生不等于另一个故障域的完整备份

设池由两个双盘 mirror 顶层 vdev 组成,先按组判断故障:

损坏分布每组还有副本吗?典型结果
每组各坏一盘有通常仍可服务
同一组两盘都失去该组没有池可能不可用

同样坏两盘,位置不同,结果就不同。RAIDZ 也需按组布局、实际错误和剩余副本分析。见 池与 vdev 概念。

2. 软件层不是所有请求都依次穿过的流水线

ZPL 将 POSIX 文件操作接到 Linux VFS;DMU 管理对象、数据缓冲及事务;SPA 和 vdev 相关层处理存储池分配与底层 I/O。ARC 是缓存机制,ZIL 是同步意图日志,二者不应作为必经串行层画在 DMU 与 SPA 之间。

OpenZFS 在 Linux 上提供树外内核模块,安装可以使用 DKMS 或发行版预编译模块。SPL 主要适配所需的内核环境和抽象,不是使任何许可证组合自动成立的法律转换层。OpenZFS 使用 CDDL,发布和打包应查其许可证说明及目标发行版政策;“永远不能合入”是超过当前事实的断言,不作为技术结论。

ARC 与 Linux 映射/页缓存路径的交互受实现版本影响,不能简单断言 ZFS 完全不涉及 Linux 页缓存。普通文件、mmap、zvol 和直接 I/O 也不应被强行套进同一条读写函数链。

3. TXG:提交一组一致的块引用

修改首先进入内存中的对象状态,并加入 transaction group。同步 TXG 时写出数据及受影响的元数据路径,最后建立可识别的有效根状态。恢复依赖块校验、顺序、有效 uberblock 等机制,不是每个块指针写入都天然原子,也不是任意扇区撕裂都无关紧要。

TXG 聚合多个修改以摊薄成本;open、quiescing、syncing 等阶段允许不同组的工作交叠。超时参数只是触发条件之一,脏数据压力等也会推动同步,不能把一次 write 对应成“正好五秒后一个原子事务”。文件系统事务边界也不自动对应应用的多步骤业务边界。

同步请求与 TXG:先建立恢复依据,再完成常规落盘 应用同步写 / fsync 内存中的修改 加入事务组 TXG 提交所需 ZIL 记录 可位于池内或独立 SLOG 同步请求可返回 恢复承诺已满足 TXG 写入常规存储 提交有效的新根 对应日志可回收 不再需要重复恢复 这是典型 ZIL 路径;已同步 TXG 等情况不必重复写日志。周期不是固定丢失窗口。

同步请求通常利用 ZIL 建立崩溃后可重放的依据,无需每次都等待整个大型 TXG 的完整同步。之后相应数据仍通过正常 TXG 路径写到常规存储;恢复时才按需要重放尚未由已提交 TXG 覆盖的日志。ZIL 记录可能包含写入数据,也可能引用已写出的数据块,不能说它“只存操作名称,不存任何数据”。

把 TXG 与 ZIL 放到同一条恢复时间线上

设已经稳定提交的 TXG 100 中,文件某个范围内容为 O。应用执行同步修改 W₁,将它写为 N。此后还执行了另一个范围的普通异步修改 W₂,但没有为 W₂ 请求同步。假设 sync=standard,存储设备遵守写入和同步承诺。

崩溃位置已稳定的恢复依据可以据此承诺什么
W₁ 的同步请求尚未成功返回至少有 TXG 100;日志是否完整取决于进度不能把 W₁ 当作已经确认的更新
W₁ 所需日志已稳定,同步调用成功;新 TXG 尚未提交TXG 100 加可重放的 W₁ 日志恢复旧根后重放所需日志,保留已确认的 W₁
TXG 101 已包含 W₁ 并稳定提交新的有效根及其引用块W₁ 已由常规树覆盖,对应日志可以回收

在下面选择修改范围,并交错同步写、异步写与 TXG 提交。恢复结果来自稳定根和仍需重放的日志;最近的内存内容并不自动成为恢复依据。

正在呈现知识画面
同步写尚未进入 TXG,恢复靠什么?

已确认的同步修改可以由旧 TXG 加所需 ZIL 记录恢复;新 TXG 覆盖后才不再需要这些日志。异步写也可能随 TXG 持久化。

再看异步 W₂:未承诺不等于必丢失

W₂ 的命运不能仅凭“它发生在 W₁ 后面”推断:若它尚未被有效 TXG 覆盖,也没有同步承诺,就可能丢失;若已随某个 TXG 稳定提交,也可能保留下来。异步的含义是应用没有在那个返回点得到相同持久性承诺,不是内核保证它一定丢失。

这条时间线说明 COW 和 ZIL 各自提供什么。COW 的有效旧根让系统能回到一组结构一致的块引用;ZIL 把已经向应用确认、但尚未进入最新常规树的同步操作补回去。日志设备的低延迟影响建立后一种恢复依据的速度,不能让主存储永久免于写出修改。

日志失去时,哪一条恢复依据不再成立

如果同时失去尚需重放的唯一日志副本,第二行的证明就不再成立。风险集合由“哪些已确认操作仍依赖该日志”决定,既不是固定墙上时间的五秒,也不是由某个参数名字单独决定。冗余日志与设备断电保护的意义,是让恢复依据在设计的故障条件下仍能存在。

4. ZIL、SLOG、L2ARC 与 special vdev

名称作用失效时首先考虑什么
ZIL同步修改的恢复日志机制已确认而未被 TXG 覆盖的修改如何恢复
SLOGZIL 使用的独立日志设备日志冗余、断电保护、错误与恢复路径
ARC内存缓存,包含数据和元数据等内存压力、缓存命中与脏数据节流
L2ARC / cache vdevARC 之外的二级读缓存丢失后通常从主存储重读,不是唯一数据源
special vdev分配元数据等,按配置也可放小数据块属于关键分配设备,不是可随时丢弃的缓存

SLOG 不能把任意异步大文件写入都变成持续 SSD 速度;它主要改变相关同步请求的延迟路径,常规存储仍要承接 TXG 写出。若后端长期排出速度不足,系统最终仍会节流。设备延迟还要看 flush 和断电保护下的真实表现,而非只看标称顺序带宽。

丢失日志设备并再遇到崩溃,可能破坏已向应用确认但尚需日志恢复的修改;不能承诺“只丢最近固定五秒,异步写不受影响”。在运行中、导入时、日志是否有冗余、相应 TXG 是否已提交,都会改变结果。相关设备职责见 OpenZFS 硬件说明。

sync=standard 遵守应用同步请求,sync=always 扩大同步要求,sync=disabled 会忽略这种请求的正常持久性要求。把 disabled 的成绩与正常 fsync 成绩直接比较,相当于比较不同的承诺,不是证明同样安全条件下更快。属性语义见 zfsprops(7)。

5. 快照、记录大小与空间保留

快照保留旧块引用,克隆可在快照基础上产生可写分支。假设文件原有块 A、B,创建快照后当前版本将 A 改成 A′,旧 A 仍被快照引用;删除当前文件不一定回收 A、B。池的使用量与某目录文件逻辑大小因共享、压缩、快照及元数据而不同。

空间账:512 KiB 文件为何保留了 640 KiB

限定一个模型:文件由四个 128 KiB 记录 A、B、C、D 组成,忽略元数据、压缩、填充和额外副本。创建快照 S 后,两个视图共享这四个记录;当前文件再把 A 改写成 A′。

一次记录改写:两个视图,共保留五个记录 快照 S 当前文件 A B C D A′ B、C、D 继续共享 5 × 128 KiB = 640 KiB 每个视图逻辑长度仍为 512 KiB;忽略元数据、压缩、填充和额外副本。
状态当前文件逻辑大小两个视图合计保留
创建快照后512 KiBA、B、C、D,共 512 KiB
改写 A 后512 KiBA、A′、B、C、D,共 640 KiB

新增占用来自历史 A 仍有引用,而不是文件逻辑长度变大。

此时删除当前文件,只要 S 还在,旧的 A、B、C、D 仍有引用;删除 S 但保留当前文件,则旧 A 可以在完成相应回收后释放。实际统计还包含其他开销和共享关系,不能直接套这个数字,但引用账的推理方法相同:⁠文件逻辑长度统计内容视图,池使用量统计仍需保留的实际存储⁠。这也解释了为什么快照刚创建很便宜,长期频繁改写后却可能保留很多历史块。

记录大小:布局属性不等于每次设备请求大小

recordsize 是文件系统数据记录大小相关属性,volblocksize 用于 zvol。它们不是“每个应用 write 必定发出同样大小的设备请求”。小范围随机改写、压缩、COW 和现有文件布局会影响放大;修改属性也不能简单当作立即重写所有旧文件。调优前应先说明工作负载的读写范围与同步频率。

缓存观测:命中率之外还要看同步长尾

ARC 通过近期/频繁访问及 ghost 历史等信息调整缓存策略;ghost 记录不是又保存一份完整数据。ARC 命中率高也可能掩盖少量同步写的长尾延迟,必须按工作负载分开观察。L2ARC 有索引、填充与内存开销,扩大它不自动修复热点工作集之外的所有性能问题。

6. 校验、scrub、resilver 与备份

机制主要解决的问题
校验发现内容不符,有正确副本时支持修复
scrub主动检查已分配数据完整性
resilver恢复所需冗余
快照与独立备份提供相应历史恢复与故障域保护

内容通过校验,不代表应用写入的业务含义正确,也不能撤销正常执行的误删除。

文件系统快照提供存储层的时间点视图,但不会自动让数据库内存状态与多个外部系统一起提交。应用一致的恢复点还需要数据库备份接口、检查点或明确的协调协议。

send/receive 传递快照状态或差异,可以构建另一池或另一主机的副本;增量链需要匹配的基准与保留策略。能生成发送流不等于已完成可恢复备份,接收端是否可用及恢复流程仍需验证。

7. 只读读取 ARC,而不修改池

from pathlib import Path

path = Path("/proc/spl/kstat/zfs/arcstats")
if not path.exists():
    print("未暴露 ARC 统计;可能未加载 OpenZFS")
else:
    values = {}
    for line in path.read_text().splitlines():
        columns = line.split()
        if len(columns) == 3 and columns[2].isdigit():
            values[columns[0]] = int(columns[2])
    for name in ("size", "c", "c_min", "c_max", "hits", "misses"):
        print(name, values.get(name, "未提供"))
    hits, misses = values.get("hits", 0), values.get("misses", 0)
    if hits + misses:
        print("累计命中比例:", hits / (hits + misses))

这里是累计比例,不能代替指定工作负载时间段的差分。zpool status 用于了解拓扑与错误;zpool iostat 用于设备/池活动;zfs get 用于核对实际属性和继承来源。先结合拓扑、属性和业务负载解释观测,再决定是否需要更改配置。