> For the complete documentation index, see [llms.txt](https://freebsd-journal-cn.bsdcn.org/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://freebsd-journal-cn.bsdcn.org/20150304-zfs-zui-jia-shi-jian/zfs-best-practices.md).

# ZFS 最佳实践

* 原文：[ZFS Best Practices](https://freebsdfoundation.org/our-work/journal/browser-based-edition/zfs-best-practices/)
* 作者：**Allan Jude**

ZFS 以其可靠性和保护数据免受可怕的位腐烂（bitrot）而闻名。然而，相当多的用户遇到了麻烦，因为他们没有理解 ZFS 与此前用过的所有文件系统有多大不同。本文旨在为使用 ZFS 列出若干最佳实践，帮你避免这些常见误区。本文涵盖硬件、配置、调优、特性，还有其他一些技巧。

## 当心硬件 RAID

人们犯的最昂贵的错误，是没有让 ZFS 掌控冗余。最好由 ZFS 提供冗余（使用 ZFS 镜像或 RAID-Z），而不是使用硬件 RAID 控制器。ZFS 中重要的数据会存储多份，称为 ditto block。池范围的数据有三个 ditto block（即存储三份），文件系统元数据有两个 ditto block。此外，ZFS 故意将 ditto block 存储到不同磁盘上，使任何单盘故障都不会导致所有元数据副本丢失。这让 ZFS 能更好地了解硬件实际状况，可纠正更多类型的错误。

当在硬件 RAID 之上创建 ZFS 存储池时，RAID 控制器向操作系统（进而向 ZFS）呈现的是逻辑卷。当 ZFS 只有一块磁盘可工作时，它无处存储故障后重建阵列所需的奇偶校验信息，也无法确保 ditto block 落在不同磁盘上。虽然这看似不是大问题，因为底层 RAID 控制器可以从硬件 RAID 层面存在的奇偶校验重建缺失的磁盘，但使用硬件 RAID 意味着你无法享受 ZFS 块级 resilver 流程带来的好处。硬件必须 resilver 整块磁盘上的每一个字节，而非仅 resilver 包含活动数据的块。硬件 RAID 在 ZFS 擅长的场景中也无能为力：检测和修复数据或文件系统本身中翻转的位和其他类型的不可检测错误。ZFS 的校验和是其最强大的特性之一，但如果没有 ZFS 冗余，它只能检测错误，无法修复。

硬件 RAID 控制器还可能引发无数其他问题，使用更便宜的简单 HBA 控制器反而好得多。硬件 RAID 控制器有时会掩盖某些错误并静默重试命令，对 ZFS 隐藏这些信息。这主要是过去的遗留做法，那时无法信任操作系统能优雅处理错误。没有这些信息，ZFS 无法做出明智决策，也无法通过从奇偶校验位置读取数据来自我修复故障盘。

即便尝试把 RAID 控制器当作简单 HBA（“IT”或“JBOD”模式）使用，许多控制器也要求先为每块磁盘创建单盘 RAID 0（或 JBOD）才能使用。在这种情况下，更换故障磁盘时不能简单地把磁盘从机箱中抽出，操作员必须使用适配器厂商提供的软件，用替换的磁盘创建新卷。这样的工具可能不存在，或在 FreeBSD 上不受支持，需要重启访问适配器提供的 BIOS 级工具。突然之间，你的热插拔硬盘仍需重启，并未提供你所期望的高可用性。ZFS 的另一项优势是存储池可移植性。存储池中的所有磁盘都可以移到另一台服务器，轻松导入并在短时间内恢复运行。然而，如果磁盘由厂商 A 的 RAID 工具打标，厂商 B 的 RAID 控制器将无法访问。如果控制器故障且无法替换为相同控制器，这可能使磁盘无法读取。

如果某些外部因素绝对要求你使用硬件 RAID，务必向 ZFS 呈现一对用于镜像的逻辑卷，或 3 个及以上卷用于 RAID-Z。硬件 RAID 提供的额外冗余会以容量和性能为代价，但不利用 ZFS 更好的冗余最终会让你后悔。此外务必在控制器上禁用“write back”模式。如果不禁用 write back，ZFS 依赖的 cache flush 命令（用于确保所有待写数据真正写入磁盘）会被忽略，可能造成数据丢失。归根结底，硬件 RAID 之上的 ZFS 在增加复杂性的同时降低性能，而复杂性的增加意味着风险上升。

## ECC 内存

许多文章和帖子会斩钉截铁地说，要使用 ZFS 就必须用 ECC 内存。引用 ZFS 联合创始人 Matt Ahrens 的话：“在没有 ECC 的系统上使用 ZFS，并不比在有 ECC 的系统上使用其他文件系统更危险。”ZFS 的特性极具吸引力且异常有用，无论机器扮演什么角色。ZFS 提供的数据保护胜过其他所有文件系统，即便没有 ECC 内存加持也是如此。不要被 FUD 吓退而不敢使用 ZFS，哪怕只是在你笔记本的单块磁盘上。只是务必做好细致的备份。

如果你的目标是高可用，ECC 多出的成本很容易证明其合理性。服务器级硬件使用 ECC 内存，因为它能降低内存出错的风险、这些错误可能导致的程序崩溃。使用 ECC 内存可降低（但并非完全消除）数据在传输过程中——离开应用程序之后、ZFS 计算校验和并将其写入稳定存储之前——发生损坏的风险。如果你关心系统的可靠性和可用性，无论使用什么文件系统，都应使用 ECC 内存。

## 仍需备份

虽然 ZFS 的可靠性和持久性让我们对数据安全得多，但对自己存储在 ZFS 存储池上的数据保持恰当备份的人仍然太少。再多的 RAID 冗余也比不上一份备份。误删了错误的存储池？你的备份在哪里？更换故障盘时抽错了盘？你的备份在哪里？把多余的盘作为新 vdev 加入而非附加到现有镜像？你的备份在哪里？

ZFS 提供了若干特性，让备份更轻松、更安全。其一是瞬时快照。与其备份文件可能不断变化的活跃系统，不如对该系统做递归快照后再备份。这样，备份中的所有内容都按某个精确时刻的状态保存，即便备份耗时多日。另一项强大的备份特性是内置的增量复制系统。此方法的第一个优势在于，与用 Bacula 或 rsync 遍历文件系统相比，ZFS 遍历其内部数据表示，从而快速连续读取块。`zfs send` 的输出可以是增量的或全量的。在增量模式下，输出保留数据流的写时复制（copy-on-write）特性，包括所有快照。此输出既可以存为大型二进制块（ZFS 数据流），也可以通过管道传给 `zfs receive`，在另一台机器上重建数据集的副本，相当于为该数据集创建了“热备件”。ZFS 还有另一项非常适合做实时备份的特性：存储池拆分。当你的存储池由镜像 vdev 组成，理想情况下每个镜像集中至少有 3 块设备，以便此操作期间存储池不会因单设备故障而陷入风险时，可以用 `zpool split` 命令从每个镜像 vdev 中分离一台设备，创建新存储池。这个新存储池包含每个 vdev 中的一块盘和当前存储池的所有数据。然后这些磁盘可以弹出并移到异地，类似磁带备份。换入新磁盘，重新加入镜像集并 resilver。持续重复这一过程，就提供了完整的异地备份，每次只需几秒创建。一旦超过你的备份保留阈值，磁盘即可重复使用。与常规备份相比，存储池拆分法对性能的影响可能更小，因为备份时间分摊到新盘加入存储池之后的整个时期。新数据写入存储池时，会镜像到常规镜像设备，但也会镜像到下次备份拆分时将被拆出的设备。在这种设置下，调整 resilver 节流（`vfs.zfs.resilver_delay`）可能是明智的，以避免拆分后加入新盘时对性能造成不当影响。

## 磁盘标签

有多种方法处理磁盘标签，并在操作系统暴露的设备与机箱中物理位置上的物理磁盘之间建立逻辑连接。在生产环境中对我来说最有效的是物理槽位号加磁盘序列号。例如，我们一台机箱前部有 24 块盘，后部还有 12 块。前部 6 号槽的盘是 f06-WMC1F125320，后部某盘可能是 r02-9WM7HATN。使用序列号有诸多理由，主要是库存和保修管理；要查盘是否仍在保修期内并申请更换，需要序列号。它是方便的唯一标识符，不过视型号和盘厂商而定，可能有点难处理。我的一块 Intel SSD 序列号是 CVDA333604282403GN，很难把它贴到 2.5 英寸盘架前部的标签上，也不符合 15 字符的 GPT 标签限制。最好截断并保留最高有效位，所以如果序列号有共同的开头，就使用序列号的末尾字符。槽位号作为前缀有助于操作员和数据中心技术员更快识别正确的物理盘，而不必比对整个序列号，尤其是多块盘序列号可能有共同前缀时。下一步是改变操作系统对该盘的表示以匹配。推荐做法是使用 GPT 分区标签，如下：

```sh
gpart modify -i 2 -l f01-9WM6T60L da0
```

这会为 da0 创建别名 **/dev/gpt/f01-9WM6T60L**

注意：标签的最大长度为 15 字符。

这样，即使操作系统识别设备的顺序改变，设备名也保持不变。这也意味着 `zpool status` 的输出会显示每块盘及其位置和序列号。借助此技术，缺失的磁盘会一目了然，向操作员或数据中心技术员说明哪块盘需要更换也很容易。

FreeBSD 提供多种磁盘标签方式，禁用未使用的可能有帮助，以避免 ZFS 拾取那些设备名：

**/boot/loader.conf**：

```ini
kern.geom.label.disk_ident.enable=1
kern.geom.label.gptid.enable=0
```

Disk Ident 示例：

```sh
/dev/diskid/DISK-07013121E6B2FA14
/dev/diskid/DISK-%20%20%20%20%20WD-WCC131365642
/dev/diskid/diskid/DISK-%20%20%20%20%20%20%20%20%20%20%20%20Z300HTCE
```

GPT ID 示例：

```sh
/dev/gptid/b829bf8c-46ad-11e3-ae0f-002590721162
/dev/gptid/b88eeff5-46ad-11e3-ae0f-002590721162
```

注意乍看之下两者似乎相同。差异在字符串开头而非结尾。

Disk Ident 和 GPTID 的另一劣势是分区标识会被附加到末尾，所以磁盘的第 2 个分区会与磁盘的唯一 ID 混在一起：

```sh
/dev/diskid/DISK-07013121E6B2FA14p2
```

在带有 SAS expander、双端口磁盘和多控制器的更高级设置中，每块盘可能因每条唯一路径而向操作系统呈现多次。

FreeBSD 的 GEOM 存储管理层为此提供了一套系统：gmultipath。它会向磁盘写入唯一标签，然后当出现两个或更多带有相同标签的设备时，将它们归类为同一物理盘的多条路径。

最终，看起来类似这样：

```sh
# gmultipath status
multipath/f01-WMC1F125320 OPTIMAL da0 (ACTIVE)
da36 (PASSIVE)

multipath/f02-WMC1F125298 OPTIMAL da1 (ACTIVE)
da37 (PASSIVE)

multipath/f03-WMC1F125506 OPTIMAL da2 (ACTIVE)
da38 (PASSIVE)
```

## 准备磁盘

创建存储池之前，需要先准备磁盘。有不少指南、博客和其他资源声称 ZFS 应总是用于整块磁盘而非分区。这在 Solaris 中成立（因为磁盘缓存的工作方式），但在 FreeBSD 下并非如此。此时有几点需要考虑。如果系统将从该存储池引导，则所有磁盘都应包含 ZFS 引导代码。一旦发生故障，可能无法预测系统会尝试从哪块盘引导。freebsd-boot 分区应为 512KB；这略低于 FreeBSD ZFS 引导块施加的最大值。使用最大值的目的是确保为引导块随时间增长留出足够空间。在磁盘上创建的所有分区都应对齐到 4k 边界，以保持分区布局在所有磁盘上一致。可在创建分区时对每个 `gpart` 命令使用 `-a 4k`。

```sh
gpart create -s gpt ada0
gpart add -t freebsd-boot -l bootfs0 -s 512k -a 4k ada0
gpart add -t freebsd-swap -l swap0 -s 2g -a 4k ada0
gpart add -t freebsd-zfs -l f01-9WM6T60L -a 4k ada0
gpart show -l ada0
=> 34 7814037100 ada0 GPT (3.7T)
34 6 - free - (3.0k)
40 1024 1 bootfs0 (512k)
1064 984 - free - (492k)
2048 4194304 2 swap0 (2.0G)
4196352 7809839104 3 f01-9WM6T60L (3.7T)
7814035456 1678 - free - (839k)
```

即便当前磁盘使用 512 字节扇区，将来要获取 512 字节扇区磁盘来替换它们可能并不容易，因此基于 4k 扇区建立整个存储池可确保将来更换磁盘时不会出现麻烦。出于同样目的，将容纳 ZFS 数据的分区创建得略小于磁盘可用空间。这部分额外空间可用作交换分区。这种余量在替换盘与原盘扇区数略有差异时提供了一些回旋余地。最后，ZFS 存储池本身应使用 4k 扇区。再说一次，即便你的盘是 512 字节扇区，将来的替换盘可能不是，且无法混合扇区大小，也无法在存储池创建后更改 ZFS 扇区大小。要强制 ZFS 使用 4k 扇区大小，在创建存储池之前设置 sysctl `vfs.zfs.min_auto_ashift=12`（2^12 = 4k）。4k 扇区的劣势是空间效率略低，对很小的读写（小于 4k）性能可能更差。对于数据库类工作负载和小于 1 TB 的磁盘，512 字节扇区可能更合适。

某些磁盘型号包含“XP Jumper”跳线，会将每个 LBA 地址偏移 1，使第一个 MBR 分区的默认起始位置（第 63 扇区）变为第 64 个 512 字节扇区，从而对齐到 4k。但是，如果设置了此跳线，再要求分区工具将分区对齐到 4k，所有分区都会偏离 1 个扇区，反而会导致我们原本想避免的性能损失。

## 存储池布局

确定存储池中磁盘和 vdev 的最佳布局方式，是用户创建新存储池时面临的最困难问题之一。

需要考虑的因素很多，而决定一旦做出通常无法更改。最大的几个因素是：随机 I/O 性能、流式性能、空间效率和容错能力。每种配置提供不同益处。就随机读取的 IOPS 性能而言，最佳方案永远是更多 vdev。镜像组（相当于 RAID 10）提供最佳性能，因为每个 vdev 的 IOPS 实际受限于其中最慢的设备，所以 12 块盘组成 6 个镜像组提供的 IOPS 是单盘的 6 倍，而 12 块盘组成单个 RAID-Z（1、2 或 3）只提供单盘的 1 倍 IOPS。将 12 块盘配置为 2 个 RAID-Z2 vdev，甚至 3 个或 4 个 RAID-Z1 vdev，可获得更高性能，代价是可用空间减少，但性能永远达不到镜像组的水平。就流式性能而言，IOPS 的影响要小得多，主轴数才是关键。

假设一组适中的商品级机械盘，每块 1 TB，可提供 250 IOPS、100 MB/s 流式读写：

| 磁盘数 | 配置              | 读取 IOPS | 写入 IOPS | 读取 MB/s | 写入 MB/s | 可用空间  | 容错         |
| --- | --------------- | ------- | ------- | ------- | ------- | ----- | ---------- |
| 2   | 1x 2 盘镜像        | 500     | 250     | 200     | 100     | 1 TB  | 1          |
| 3   | 1x 3 盘镜像        | 750     | 250     | 300     | 100     | 1 TB  | 2          |
| 3   | 1x 3 盘 RAID-Z1  | 250     | 250     | 200     | 200     | 2 TB  | 1          |
| 4   | 2x 2 盘镜像        | 1000    | 500     | 400     | 200     | 2 TB  | 1 (2\*)    |
| 4   | 1x 4 盘 RAID-Z1  | 250     | 250     | 300     | 300     | 3 TB  | 1          |
| 5   | 1x 5 盘 RAID-Z1  | 250     | 250     | 400     | 400     | 4 TB  | 1          |
| 5   | 1x 5 盘 RAID-Z2  | 250     | 250     | 300     | 300     | 3 TB  | 2          |
| 6   | 3x 2 盘镜像        | 1500    | 750     | 600     | 300     | 3 TB  | 1 (3\*)    |
| 6   | 2x 3 盘镜像        | 1500    | 500     | 600     | 200     | 2 TB  | 2 (4\*\*)  |
| 6   | 1x 6 盘 RAID-Z1  | 250     | 250     | 500     | 500     | 5 TB  | 1          |
| 6   | 1x 6 盘 RAID-Z2  | 250     | 250     | 400     | 400     | 4 TB  | 2          |
| 12  | 6x 2 盘镜像        | 3000    | 1500    | 1200    | 600     | 6 TB  | 1 (6\*)    |
| 12  | 4x 3 盘镜像        | 3000    | 1000    | 1200    | 400     | 4 TB  | 2 (8\*\*)  |
| 12  | 2x 6 盘 RAID-Z1  | 500     | 500     | 1000    | 1000    | 10 TB | 1 (2\*)    |
| 12  | 2x 6 盘 RAID-Z2  | 500     | 500     | 800     | 800     | 8 TB  | 2 (4\*\*)  |
| 36  | 18x 2 盘镜像       | 9000    | 4500    | 3600    | 1800    | 18 TB | 1 (18\*)   |
| 36  | 12x 3 盘镜像       | 9000    | 3000    | 3600    | 1200    | 12 TB | 2 (24\*\*) |
| 36  | 1x 36 盘 RAID-Z2 | 250     | 250     | 3400    | 3400    | 34 TB | 2          |
| 36  | 2x 18 盘 RAID-Z2 | 500     | 500     | 3200    | 3200    | 32 TB | 2 (4\*\*)  |
| 36  | 4x 9 盘 RAID-Z2  | 1000    | 1000    | 2800    | 2800    | 28 TB | 2 (8\*\*)  |
| 36  | 6x 6 盘 RAID-Z2  | 1500    | 1500    | 2400    | 2400    | 24 TB | 2 (12\*\*) |

\* 前提是每个 vdev 故障数不超过 1 \*\* 前提是每个 vdev 故障数不超过 2

使用更多数量的小磁盘组会以减少可用空间（更多奇偶校验）为代价提升性能。流式读写性能受限于非奇偶校验主轴的数量。这就引出了对随机和流式性能都要考虑的另一因素：主轴数。一组 12 块 1 TB 盘通常会比 6 块 2 TB 盘表现更好，因为更多的主轴数既提升 IOPS 也提升流式性能。

## 避免单点故障

避免单点故障会提升存储池的可用性。通过一些规划和明智的设计决策，同样的硬件可以组织成容错能力更强的配置。在更大型的安装中，所有磁盘可能并不位于同一物理机箱内，应考虑哪些磁盘属于哪个 vdev。一台前部带 36 盘、外加 3 个各 36 盘的外部 JBOD 机箱的服务器，应配置为每个 RAIDZ2 vdev 由来自每个 JBOD 的 2 块盘组成（共 18 个 vdev，每个 6 块盘）。在这种配置下，即便某个 JBOD 的电源、HBA 或线缆故障，每个 vdev 仍能正常工作。而如果配置为 vdev 由同一 JBOD 内的盘组成，多个 vdev 会故障，系统无法继续运行。同样的思路也可用于更小的系统，这些系统可能没有多路径来容忍 HBA 故障。如果构建镜像对，确保每块盘与另一块不在同一控制器上的盘配对，这样，控制器故障只会让每个受影响 vdev 降级而非故障。

## 压缩

ZFS 支持透明压缩，数据写入磁盘时压缩，读回时解压，用户或应用程序无需感知。除了显而易见的存储利用率降低之外，还能提升性能。即便有少量 CPU 使用开销，压缩数据能以与未压缩数据相同的速度从磁盘读取，但一旦解压，就能提供更高的有效吞吐量。如果磁盘可读取 100 MB/s，数据压缩率为 50%，那么这些数据现在实际上可以以 150 MB/s 的速度读取。写入同理，吞吐量提升、延迟降低，因为写入更少的数据耗时更短。这让压缩数据集对数据库极有用——数据库往往包含高度可压缩的文本，且总是受益于更高的吞吐量和更低的延迟。ZFS 中使用的较新 LZ4 压缩算法还有“早退”特性：如果块首位的压缩率低于 12.5%，则以未压缩方式存储该块。这进一步降低了使用压缩对性能的影响，因为不可压缩文件会被快速跳过。考虑到这些，可考虑在整个存储池上使用 LZ4 压缩。

## 去重

ZFS 支持在线去重，即数据写入时去重。虽然此特性看似诱人，但你会看到它相当昂贵。原因如下。为了在写入时去重数据，ZFS 为每个块的 SHA256 校验和构建一张哈希表（称为去重表，DDT）。DDT 作为 ARC（Adaptive Replacement Cache）的一部分存储在主存中。新块排队写入时，先与 DDT 比较，若找到匹配项，只需增加现有块的引用计数，而不写出该块。若未找到匹配，则写入新数据并将新校验和加入 DDT。DDT 中每条记录占用 320 字节或更多内存。如果 ARC 的元数据部分没有足够空间，DDT 条目会写入 L2ARC（通常是 SSD 或其他用作二级缓存的快速存储设备），在那里占用更多空间。在典型的 zvol 支撑的 iSCSI target 场景下，块大小为 8 KB，意味着 1 TB 唯一数据会产生近 48 GB 的 DDT。如果内存装不下，就存入 L2ARC，但那也有内存开销。此外，DDT 条目在磁盘上比内存中占用更多空间，性能也更差。如果存储池出问题，导入存储池时 DDT 必须能装入内存，若装不下，存储池可能无法导入。DDT 和 L2ARC 映射也算作元数据，默认被限制为仅可用 ARC 内存的 1/4。如果打算使用去重，需要调整元数据上限。去重的收益代价高昂——如果你的数据集只能获得中等的去重率，多半用 LZ4 压缩会好得多。如果我仍未劝退你使用去重，你应该对自己的数据做一些测试，确保获得非常好的去重率，并计算所有数据的 DDT 需要多少内存。务必在 ARC 中为实际元数据、DDT 和 L2ARC 索引、当然还有实际数据缓存留出空间。你需要大量内存。

## 预留

ZFS 中存储的池化特性意味着所有可用空闲空间都可供每个数据集使用。与传统的把 RAID 卷分区或创建独立卷的做法相比，空闲空间不会碎片化。缺点是单个工作负载或用户可能耗尽所有可用空间。虽然可以用配额解决，但并不总能解决问题。ZFS 还提供预留系统，可以为关键数据库或安全日志等特定数据集保留最低限度的空间，对其他任何数据集不可用。为确保电子商务数据库不会因 HTTP 日志而耗尽空间，给它的数据集预留。

虽然近期 ZFS 的改进大大改善了情况，但接近满的 ZFS 存储池性能会很差。如果存储池完全满了，解决该情况的命令可能耗时极长。应对方法是创建名为“reserved”的新数据集，预留存储池总容量的 20% 到 25%。这可防止最后那一点关键空间耗尽。预留可以放宽，让管理员执行必要操作或维持系统运转，直到存储池可以扩容。

## 调优

ZFS 中最大的变量是 ARC 的大小。ARC 是 ZFS 存储最近使用和频繁使用数据、元数据的地方。ARC 提供了 ZFS 带来的大部分出色性能。ARC 的最大大小默认为系统中所有内存减去 1 GB（或在内存较少的机器上为所有内存的 1/2）。对于专用文件服务器，这合理；但如果还有其他需要内存的应用程序，你可能想把它调小一些，为 web 服务器或数据库服务器等其他应用留出空间。该上限通过 **/boot/loader.conf** 中的可调参数 `vfs.zfs.arc_max` 设置。一般最好为操作系统和应用程序至少留出几 GB 内存，但最大化 ARC 可用内存会提升性能。ARC 检测到内存压力时会向操作系统归还内存，但这并非瞬时完成，可能造成严重交换。如前所述，元数据的默认上限是 ARC 的 1/4。如果你的数据集包含大量小文件，提高这个值可能有利。

希望这些技巧和最佳实践对你有所帮助，帮你避免一些 ZFS 新手常犯的错误。我们希望看到你加入蓬勃发展的 OpenZFS 用户和开发者社区。特别感谢备份大师 Dan Langille 和 Michael Dexter——他为各种客户包扎过许多自己造成的脚伤，并分享了所学。

***

**Allan Jude** 是 ScaleEngine Inc.（一家全球 HTTP 和视频流 CDN，内容分发网络）的运营副总裁，在那里大量使用 FreeBSD 上的 ZFS。他也是视频播客 BSD Now（与 Kris Moore 共同主持）和 JupiterBroadcasting.com 上的 TechSNAP 的主持人。Allan 是 FreeBSD doc 提交者，专注于改进手册和记录 ZFS。2007 至 2010 年间，他在加拿大汉密尔顿的 Mohawk College 教授 FreeBSD 和 NetBSD，拥有 12 年 BSD UNIX 系统管理员经验。


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://freebsd-journal-cn.bsdcn.org/20150304-zfs-zui-jia-shi-jian/zfs-best-practices.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
