起因
家用 NAS 16GB RAM,跑 ZFS + 几个 Docker 容器(Plex / Photoview /
Syncthing)。free -h 显示 used 15.2G,容器频繁因 OOM 被重启。
但 htop 加起来所有进程才用 5GB。剩下 10GB 是谁吃的?
答案:ZFS ARC(Adaptive Replacement Cache,磁盘 cache)。
ZFS 默认抢半数物理内存做 cache,且不像 page cache 那样有 application
请求时立刻让出来。
诊断
看 ARC 实际占用
arc_summary | head -30
# 或:
cat /proc/spl/kstat/zfs/arcstats | grep -E '^(c|c_max|c_min|size|hits|misses)'
输出例:
size = 9.8 GB
c = 10.4 GB
c_max = 10.4 GB # arc 上限(默认 RAM 一半)
c_min = 660 MB
hits = 24351234
misses = 234567 # hit rate ~ 99%
size 是当前 ARC 占用。c_max 是上限——ZFS 不会让 ARC 超过这个值,
但会贪婪地用到这个值。
看分类
arc_summary | grep -A 20 'ARC size'
# ARC size (current):
# MFU: 6.2 GB
# MRU: 3.5 GB
# Anon: 50 MB
MFU = most frequently used;MRU = most recently used。
free 看 ARC 在哪一栏
free -h
# total used free shared buff/cache available
# Mem: 15Gi 5.0Gi 0.2Gi 320Mi 9.8Gi 4.5Gi
ARC 主要算在 used 而非 buff/cache(与一般 page cache 不同)。
available 列才反映"应用真正可拿到多少内存"——这个 4.5GB 还行,
但 ZFS 释放 ARC 给 application 不是瞬间的,高内存压力时仍可能 OOM。
解决方案
1. 调小 ARC 上限
临时(重启失效):
# 限到 4GB
echo 4294967296 | sudo tee /sys/module/zfs/parameters/zfs_arc_max
持久(modprobe 配置 + 重新生成 initramfs):
echo 'options zfs zfs_arc_max=4294967296' | sudo tee /etc/modprobe.d/zfs.conf
echo 'options zfs zfs_arc_min=1073741824' | sudo tee -a /etc/modprobe.d/zfs.conf
sudo update-initramfs -u
sudo reboot
zfs_arc_max 单位 byte。4GB = 4 * 1024^3 = 4294967296。
2. 设 ARC 收缩门槛(让 ARC 更快让位)
# 系统 free memory < 256MB 时立刻收缩 ARC
echo 268435456 | sudo tee /sys/module/zfs/parameters/zfs_arc_sys_free
默认 ZFS 收缩很慢,对突发负载不友好。这条让 ARC 在内存紧张时更积极
释放。
3. 实际数据评估"够不够"
arc_summary | grep -A 5 'Cache hits'
# Cache hits:
# Total hits: 24.3M
# Cache miss ratio: 1.0%
99% hit rate 说明现有 ARC 大小 OK,缩小一点不会明显影响性能。
< 90% 说明 ARC 缺,缩小会让磁盘 IO 增加。
4. 应用专门的 prefetch 调优
# 预读(顺序读多 sample)
echo 0 | sudo tee /sys/module/zfs/parameters/zfs_prefetch_disable
# = 0 启用 prefetch;某些场景关掉省内存
# L2ARC:用 SSD 作二级缓存(家用通常没必要)
sudo zpool add tank cache /dev/nvme0n1p3
L2ARC 把"放不下 RAM 的 ARC 内容"扩展到 SSD。代价是 RAM 里要存 L2ARC
的元数据(约每 1GB L2ARC 占 25MB RAM)。
5. 给容器 / 重要服务设 cgroup 内存保证
# /etc/systemd/system/docker.service.d/memory.conf
[Service]
MemoryHigh=10G
MemoryLow=2G # 内存紧张时优先保住 docker 这 2GB
MemoryLow 是"低水位保护"——内核回收内存时不会减这个 cgroup 低于
2GB(除非全系统真没内存)。
sudo systemctl daemon-reload
sudo systemctl restart docker
效果
我家 NAS 调整后:
- 设
zfs_arc_max=4G,ARC 从 10GB 缩到 4GB - 释放 6GB 给 Docker 容器,OOM 消失
- ZFS cache hit rate 从 99.2% → 96.5%(轻微下降)
- 磁盘 IO 从 5 MB/s 平均 → 12 MB/s(更频繁回原盘读)
- 但容器响应延迟稳定,体验明显改善
结论:家用 NAS 16GB / 32GB 内存,ARC 给 1/4 物理内存合适。
ZFS 默认 1/2 是为"纯 NAS"设计的,混合负载机器要调。
何时不调 ARC
服务器是 dedicated ZFS file server(不跑别的)→ ARC 越大越好,
hit rate 决定性能。
服务器跑 PostgreSQL / 任何强 I/O DB → DB 自己也想要 RAM 做 buffer pool。
两者抢内存。建议给 DB 60% RAM、ARC 30% RAM,剩 10% OS。
看 cache 效果
# arcstat 实时
sudo apt install zfsutils-linux
arcstat 5
# time read miss miss% dmis dm% pmis pm% mmis mm% arcsz c
# 10:00 1.5K 2 0 2 0 0 0 1 0 4.0G 4.0G
# ...
# 第一列 read 是每秒读次数;miss% 越低越好
踩过的坑
-
改 zfs.conf 后没 update-initramfs:ZFS 模块在 initramfs 里
被加载,外面 conf 不生效。update-initramfs -u+ reboot。 -
free误判:很多人看到 ARC 占内存以为是 leak,去 kill 进程
也没用。arc_summary才说清楚。 -
L2ARC 放 HDD 上:完全没意义,L2 比主磁盘还慢。L2ARC 必须 SSD
或 NVMe。 -
ZIL(SLOG)vs L2ARC 混淆:ZIL 是同步写日志加速(SSD 推荐),
L2ARC 是读缓存扩展。不一样。 -
swap 不要放 zvol 上:ZFS 的写时复制 + 内存压力时需要 swap →
死锁。如果用 ZFS root,swap 单独建普通分区。
登录后参与评论。