ZFS 在 VPS 上占用多少内存?ARC 内存限制配置指南
在 VPS 上运行 ZFS 时,ARC 缓存常会挤占应用内存。本文分析 OpenZFS 在 FreeBSD 与 Linux 上的内存权衡,提供具体的 ARC 大小限制设置方法,帮助您在 2GB 或 4GB 内存的服务器上平衡性能与稳定性。
ZFS 的优势与代价
FreeBSD 和 Linux 上的 ZFS 现已统一为同一代码库 OpenZFS,因此两者的功能完全一致。运行 ZFS 的服务器可获得数据校验和、在数据变更前几乎不占空间的快照、通过 zfs send 实现的复制功能,以及只需设置一个属性即可启用的压缩功能。其代价是内存占用:ARC(自适应替换缓存)默认会占用大量 RAM,而在 2 GB 或 4 GB 的 VPS(虚拟专用服务器)上,这些内存正是应用程序所需的资源。
本指南从租用的单盘或双盘 VPS 角度评估 ZFS,而非拥有 40 个驱动器托架的存储服务器。只有在上述环境下依然实用的功能才值得投入精力。在构建存储池之前,了解哪些功能不适用至关重要。
OpenZFS 在 FreeBSD 和 Linux 上的表现:同一代码库,两种打包方式
自 2008 年 FreeBSD 7.0 发布以来,ZFS 就已内置于 FreeBSD 基础系统中,最初作为实验性功能提供。自 2020 年 12 月 OpenZFS 2.0 发布后,FreeBSD 和 Linux 开始使用相同的源码树进行构建,因此 zfs 和 zpool 在两者上的行为完全一致,在一个系统上创建的存储池(pool)可以直接导入到另一个系统中使用。
ZFS 在 Linux 上以软件包形式存在,而在 FreeBSD 上属于基础系统的一部分,其原因在于授权协议。OpenZFS 使用 CDDL(通用开发与分发许可证)。Linux 内核使用 GPL(通用公共许可证)第 2 版。Linux 内核项目认为两者不兼容,因此 ZFS 代码未合并至 Linux 主线内核,各发行版需自行决定如何分发。FreeBSD 不存在此类冲突,因此 ZFS 直接内置。这就是全部的实际情况:仅存在打包方式的差异,用户无需在两者之间做出选择。
SSD Nodes 不提供 FreeBSD 镜像,因此在本平台租用的服务器上,请参考本指南中关于 Linux 的部分。如果您在其他地方运行 FreeBSD,FreeBSD 服务器 将直接获得 ZFS 支持,无需构建模块,也无需担心内核升级带来的兼容性问题。
安装 ZFS 并创建存储池
在 Ubuntu 上,该模块已包含在内核软件包中,因此您只需安装相关命令。
sudo apt update
sudo apt install -y zfsutils-linux
zfs versionzfs version 会输出两行信息,分别是用户态版本和内核模块版本。如果仅显示一行,说明模块未加载。该软件包位于 universe 组件中,Ubuntu 服务器镜像默认已启用该组件;如果 apt 无法找到它,请先执行 sudo add-apt-repository universe。
在 Debian 上,软件包位于 contrib 组件中,且模块通过 DKMS(动态内核模块支持)在您的机器上进行构建。请将 contrib 添加到 /etc/apt/sources.list.d/debian.sources 文件中的 Components: 行,运行 sudo apt update,然后执行:
sudo apt install -y linux-headers-$(dpkg --print-architecture) zfs-dkms zfsutils-linux安装过程会编译模块并输出 Building initial module for 6.12.0-...,这需要几分钟时间。请注意:每次内核升级都会触发重新构建,如果构建失败,在修复之前您的存储池将无法挂载。
在 FreeBSD 上,无需安装任何内容。只需启用并启动服务即可。
sysrc zfs_enable=YES
service zfs start现在创建存储池。请先查看稳定的设备路径,因为 /dev/vdb 是按检测顺序分配的,当您挂载其他卷时,路径可能会发生变化。
ls -l /dev/disk/by-id/
sudo zpool create -o ashift=12 tank /dev/disk/by-id/virtio-abc123def456
zpool status tankzpool status 应该输出 state: ONLINE,且您的设备应列在 tank 下。ashift=12 将存储池的最小块大小固定为 4 KiB,这与当前的 SSD 相匹配,且创建后无法更改。
大多数租用的镜像从 ext4 根分区启动,因此这里的 ZFS 是作为第二块卷上的数据池,而非根文件系统。在构建之前,请务必确认设备是否正确,因为 确认您购买的 NVMe 磁盘 只需一分钟,而重建则需要一下午。
仅当存储池具有冗余时校验和才能进行修复
ZFS 写入的每个数据块都带有校验和,每次读取都会进行验证。检测功能始终有效,但修复需要第二个副本。
在单盘存储池中,ZFS 会如实告知错误并停止读取。zpool status -v 的报告如下:
status: One or more devices has experienced an error resulting in data
corruption.
action: Restore the file in question if possible. Otherwise restore the
entire pool from backup.
errors: Permanent errors have been detected in the following files:
/tank/data/archive.tar系统会指出损坏的文件名。ext4 会直接返回这些错误字节而不做任何提示,因此 ZFS 的这种机制已经很有价值。但 ZFS 仍无法修复它,因为存储池中没有第二个副本可供修复。
使用镜像时,相同的读取请求会由正常的一端提供,损坏的数据块会被重写,该事件会出现在 zpool status 的 CKSUM 列中。这就是自愈功能,它需要两个设备。
sudo zpool create -o ashift=12 tank mirror /dev/disk/by-id/DISK1 /dev/disk/by-id/DISK2在 VPS 上,宿主机存储通常已经具备冗余,通常是 虚拟机管理程序下的 RAID 10。这可以防止硬盘物理损坏。但当数据块读取错误时,它无法告知用户,因为阵列无法判断哪个副本是正确的。ZFS 知道,因为它会将数据与自身写入的校验和进行比对。
如果你只有一个虚拟磁盘并希望具备一定的修复能力,sudo zfs set copies=2 tank/important 会在同一磁盘上存储该数据集每个数据块的两个副本。这会使该数据集占用的空间翻倍,它能应对坏块,但在整个卷丢失时则无能为力。
Scrub 操作会读取存储池中的所有数据并进行验证。
sudo zpool scrub tank
zpool status tank健康的存储池会以 scan: scrub repaired 0B in 00:04:11 with 0 errors 类似的行结尾。请为其设置计划任务;对于小型存储池,每月执行一次即可。
systemctl list-unit-files 'zfs-scrub*'
sudo systemctl enable --now zfs-scrub-monthly@tank.timer数据集是策略的最小单元
数据集是存储池内的文件系统,创建数据集的成本很低,因此建议为每个任务创建一个独立的数据集。属性会从存储池向下继承,这意味着你只需设置一次默认值,并在必要时进行覆盖。
sudo zfs create tank/data
sudo zfs create tank/pg
sudo zfs set compression=lz4 tank
sudo zfs set atime=off tank
sudo zfs set quota=20G tank/data
sudo zfs set recordsize=16K tank/pg
zfs get -r compression,compressratio,quota tank压缩是人们出于谨慎而忽略的属性,但这种做法是错误的。lz4 仅消耗少量 CPU 资源,却能减少写入磁盘的字节数,因此对于可压缩数据,它通常能提高读写速度。zstd 的压缩强度更高,但 CPU 开销也更大,适合用于很少读取的日志和归档文件。使用 zfs get compressratio tank 查看实际的压缩效果,并记住压缩比仅统计属性设置后写入的数据。
recordsize 是数据集写入的最大块大小,默认值为 128K。如果数据库将 8 KiB 的页面写入 128 KiB 的记录中,会导致一次小规模写入变成“读取整个记录、修改、再写回”的过程。在加载数据前,请务必在数据库数据集上设置 recordsize=16K,因为该属性仅对新写入的块生效。
quota 是防止单个数据集填满存储池的手段。当 ZFS 存储池接近 100% 满载时,性能会下降且清理工作变得困难,因此请务必预留足够的空间。
快照在数据变更前不占用空间
ZFS 绝不会覆盖实时数据块。它会写入新块并更新指针,这就是写时复制(copy-on-write)的含义。快照仅是一条记录,声明“保留此数据集当前指向的所有数据块”,因此创建快照是即时且免费的。
sudo zfs snapshot tank/data@2026-08-11
zfs list -t snapshot -o name,used,refer -r tank/data快照的 USED 列表示仅由该快照占用的空间。它初始接近零,并随着数据修改或删除而增加,因为旧的数据块无法被释放。
恢复文件无需执行还原步骤。
ls /tank/data/.zfs/snapshot/
cp /tank/data/.zfs/snapshot/2026-08-11/notes.txt /tank/data/notes.txt.zfs 目录对 ls -a 也是隐藏的,除非运行 sudo zfs set snapdir=visible tank/data。请在需要前创建快照,因为如果没有快照,一次误操作 rm -rf 会迫使你进入 ext4 恢复流程,该流程始于卸载磁盘,后续步骤更为复杂。
回滚操作会丢弃自快照创建以来写入的所有数据。
sudo zfs rollback tank/data@2026-08-11如果存在更新的快照,回滚操作会拒绝执行,此时需使用 -r 销毁这些较新的快照才能继续。按下回车键前,请务必仔细核对数据集名称。
快照不是备份。 它存在于同一个存储池、同一个卷、同一台服务器上。卷故障或 zpool destroy 会导致快照随数据一同丢失。快照可以保护你免受自身 rm 操作或升级失败的影响,这涵盖了许多实际故障场景,但它无法抵御存储池本身的任何损坏。详细论述请参考:为什么 VPS 快照不是备份。
发送与接收:单条命令实现复制
zfs send 将快照转换为标准输出上的字节流,zfs receive 则将该流还原为数据集。首次复制为全量发送。
sudo zfs snapshot tank/data@daily-2026-08-11
sudo zfs send tank/data@daily-2026-08-11 | ssh backup.example.com "sudo zfs recv -F backup/data"此后,仅发送两个快照之间的差异部分。
sudo zfs snapshot tank/data@daily-2026-08-12
sudo zfs send -i tank/data@daily-2026-08-11 tank/data@daily-2026-08-12 | ssh backup.example.com "sudo zfs recv backup/data"接收端必须保留发送源所基于的快照。若接收端缺失该快照,接收过程将因 cannot receive incremental stream: most recent snapshot of backup/data does not match incremental source 而停止,因为 ZFS 缺少应用差异的基准。请从双方均持有的快照进行发送,或重新执行全量发送。
在目标端授予权限,而非使用远程 root 账户:sudo zfs allow -u backupuser create,mount,receive backup/data。
这是一种真正的异地备份,但有一个前提条件。远端必须是 ZFS 存储池,因为对象存储无法接收此类数据流。若目标端为兼容 S3 的存储或普通 Linux 主机,请使用相应的工具,从 VPS 进行 restic 备份 涵盖了该方案。
为什么 ZFS 会占用大量内存?ARC 机制
ARC(自适应替换缓存)是 ZFS 的读取缓存。它驻留在内核内存中,而非普通的 Linux 页缓存,因此 free -h 不会将其计入 buff/cache。它会被显示为已用内存。一台看起来内存几乎占满的 ZFS 服务器,通常是因为缓存处于预热状态,这也是大多数关于“ZFS 吞噬了我的内存”报告的原因。
默认限制设置得较为宽松。OpenZFS 2.3 将 ARC 最大值设为“内存总量减去 1 GiB”与“内存总量的 5/8”中的较大值。OpenZFS 2.2 及更早版本在 Linux 上使用内存总量的一半作为上限,而 FreeBSD 此前已采用新规则。运行 zfs version 可查看当前系统适用的规则。
The data behind this chart
[
{
"label": "2 GB VPS",
"openzfs_2_2_linux_gib": 1,
"openzfs_2_3_gib": 1.25
},
{
"label": "4 GB VPS",
"openzfs_2_2_linux_gib": 2,
"openzfs_2_3_gib": 3
},
{
"label": "8 GB VPS",
"openzfs_2_2_linux_gib": 4,
"openzfs_2_3_gib": 7
},
{
"label": "16 GB VPS",
"openzfs_2_2_linux_gib": 8,
"openzfs_2_3_gib": 15
}
]上述数值是针对常见实例规格的默认规则,而非运行中服务器的实际测量值。在 4 GB 内存的实例上,2.3 规则允许的 ARC 大小为 3 GiB。同一台服务器在 2.2 版本下上限为 2 GiB。在 2.3 规则下,2 GB 内存的实例仍允许 1.25 GiB。剩余内存将分配给应用程序。
请直接从服务器读取实际数值,不要仅依赖表格:
grep -E '^(size|c_max) ' /proc/spl/kstat/zfs/arcstats
arc_summary | head -n 20第三列为字节数。c_max 是当前的强制上限,size 是 ARC 当前占用的实际大小。
ARC 会释放内存。当内核发出内存压力信号时,ARC 会自动收缩。问题在于时机,因为收缩是由压力触发的,如果某个进程一次性申请数百 MiB 内存,在 ARC 完成释放前,该进程可能会触发 OOM(内存溢出)杀手。在一台运行数据库和 Web 服务器的 2 GB 内存服务器上,这种情况并不罕见。OpenZFS 手册在提到手动调整时也指出:降低限制“若无内存压力诱导,ARC 不会自动收缩”。
如何在小型 VPS 上限制 ARC
首先确定工作负载所需的内存。将数据库和应用程序的需求相加,预留出操作系统所需的空间,将剩余部分分配给 ARC。对于运行 Postgres 和一个 Web 应用的 4 GB 实例,将 ARC 设置为 512 MiB 到 1 GiB 是一个合理的起点。
实时设置该值(单位为字节)。以下示例为 1 GiB。
echo 1073741824 | sudo tee /sys/module/zfs/parameters/zfs_arc_max使其在重启后依然生效。
echo 'options zfs zfs_arc_max=1073741824' | sudo tee /etc/modprobe.d/zfs.conf
sudo update-initramfs -uinitramfs 步骤至关重要,因为模块可能会在根文件系统挂载前从 initramfs 加载,这意味着它不会读取你刚刚写入的文件。重启后,使用 arcstats 中的 c_max 行进行确认。
手册中提到了两点注意事项。系统运行时无法将该值改回 0,因此撤销此设置需要编辑文件并重启。此外,调低数值不会立即缩减已占用的 ARC。
在 FreeBSD 上,同样的限制通过 vfs.zfs.arc 下的 sysctl 实现。运行 sysctl vfs.zfs.arc 查看当前值以及你所用版本对应的确切名称,然后将最大值写入 /boot/loader.conf。
针对小型服务器还有两条内存规则。保持重复数据删除(deduplication)功能关闭,因为重复数据删除表驻留在内存中,通常的经验法则是每 TB 唯一数据需要 1 到 3 GB 内存。此外,不要将 swap 放在 zvol(从存储池中划分出的块设备)上,因为通过正在尝试释放内存的文件系统进行交换可能会导致机器死锁。请将 swap 保留在普通分区或存储池之外的 swap 文件中。
何时使用 ext4 或 XFS 配合 restic 是更好的方案
如果服务器拥有充足的内存和第二个存储卷,ZFS 的价值显而易见。除此之外,使用标准文件系统配合专业的备份工具是更优选择。在以下情况下,请选择 ext4 或 XFS:
- 实例内存仅为 2 GB 或 4 GB,且工作负载需要占用全部内存。
- 仅有一个虚拟磁盘且没有备份副本,此时 ZFS 只能检测错误而无法修复。
- 备份目标是对象存储或普通的 Linux 主机,无法接收
zfs send流。 - 您运行的是使用 DKMS 的 Debian 系统,无法承担因内核升级导致模块构建失败的风险。
- 您需要将 ZFS 用于根文件系统,但服务商提供的镜像仅支持 ext4。
如果您拥有独立的数据卷、充足的内存(8 GB 及以上为宜),并且制定了利用快照和 zfs send 的具体方案,而非仅仅是启用它们,则应保留 ZFS。对于其他场景,ext4 配合 restic 将加密、去重后的备份写入服务器无法直接控制的存储空间,可以在不占用额外内存的情况下实现几乎相同的效果。
故障模式及对应的提示信息
重启后存储池丢失。 zpool status 会输出 no pools available。导入服务会读取 /etc/zfs/zpool.cache,因此该文件中缺失的存储池在启动时不会被自动导入。sudo zpool import 会列出可导入的存储池,sudo zpool import tank 用于手动导入,sudo zpool set cachefile=/etc/zfs/zpool.cache tank 则将其设为永久挂载。若存储池未在其他系统上正常导出,系统会报告 cannot import 'tank': pool may be in use from other system,在确认没有其他主机正在使用该存储池后,可使用 sudo zpool import -f tank 强制覆盖。
在 Debian 内核升级后出现 modprobe: FATAL: Module zfs not found in directory /lib/modules/6.12.0-...。 这是因为 DKMS 未针对新内核进行构建,通常是因为缺少对应的内核头文件。dkms status 可查看各内核版本对应的构建状态。执行 sudo apt install -y linux-headers-$(uname -r) 后再运行 sudo dkms autoinstall 即可重新构建,随后使用 sudo zpool import tank 即可恢复存储池。
存储池已满,但已删除文件。 只要快照仍引用已删除的数据,这些数据就不会从磁盘释放,导致 du 和 df 的统计结果不一致。zfs list -o space -r tank 可将磁盘占用拆分为 USEDDS 和 USEDSNAP,若 USEDSNAP 数值过大,即为空间占用的原因。使用 sudo zfs destroy tank/data@2026-06-01 删除旧快照即可回收空间。
zpool status 中的 CKSUM 计数不断增加。 说明 ZFS 下层的硬件返回了错误数据。在镜像池中,该计数仅作为警告,且损坏的数据块已被自动修复。在单盘存储池中,该文件已损坏,zpool status -v 会列出受影响的文件名,此时需从存储池之外的备份中恢复该文件。
服务器运行缓慢并频繁使用交换分区。 请按上述方法限制 ARC 大小,然后运行 arc_summary 查看命中率。如果 ARC 过小无法容纳工作集,会导致所有读取请求都直接访问磁盘;此时,使用依赖页面缓存的普通文件系统可能性能更佳。
FAQ
VPS 上 ZFS 需要多少内存?
ZFS 可以在 2 GB 内存的实例上运行。真正的问题是留给应用程序的内存有多少。在未进行调优的情况下,OpenZFS 2.3 允许 ARC 缓存增长至“内存总量减去 1 GiB”与“内存总量的 5/8”中的较大值。因此,4 GB 内存的服务器可分配 3 GiB 给缓存。请将 zfs_arc_max 设置为您的工作负载可以承受的数值,然后通过读取 /proc/spl/kstat/zfs/arcstats 中的 c_max 行来确认设置。
ZFS 快照是备份吗?
不是。快照与数据存储在同一个存储池中。它可以抵御错误的 rm 操作和升级失败,但如果存储池或实例损坏,快照也会随之丢失。若要将其转化为备份,请使用 zfs send 将其发送到另一台机器,或者运行能够将数据写入该服务器无法控制的存储介质的备份工具。
ZFS 在 FreeBSD 和 Linux 上的表现一致吗?
自 2020 年 12 月 OpenZFS 2.0 发布以来,两者使用相同的代码库、相同的命令、相同的磁盘格式,且存储池可以在两者间迁移。区别在于打包方式。FreeBSD 将 ZFS 集成在基础系统中。而在 Linux 上,各发行版处理方式不同:Ubuntu 将模块构建在内核包中,而 Debian 则通过 DKMS 在您的机器上进行构建,因此内核升级后,若重建未成功,您可能会暂时失去 ZFS 模块。
ZFS 能修复单盘 VPS 上的数据损坏吗?
ZFS 可以检测到损坏并指出具体文件,但无法修复,因为修复需要数据块的第二个副本。在数据集上启用 zfs set copies=2 可以为您提供第二个副本,代价是占用双倍空间;这可以处理坏块,但无法应对卷丢失。跨两个卷建立镜像才是真正能实现自动修复的方案。
压缩会拖慢服务器速度吗?
lz4 通常会提升速度。压缩后的数据块意味着读写的字节数更少,且每个数据块的 CPU 开销远小于节省磁盘 I/O 所带来的收益。请在存储池根目录设置 compression=lz4,以便所有数据集继承该属性,并在写入真实数据后检查 zfs get compressratio tank。