ZFS ARC 内存吃光的诊断 + 调优(家庭 NAS 16GB 内存够用)

起因

家用 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% 越低越好

踩过的坑

  1. 改 zfs.conf 后没 update-initramfs:ZFS 模块在 initramfs 里
    被加载,外面 conf 不生效。update-initramfs -u + reboot。

  2. free 误判:很多人看到 ARC 占内存以为是 leak,去 kill 进程
    也没用。arc_summary 才说清楚。

  3. L2ARC 放 HDD 上:完全没意义,L2 比主磁盘还慢。L2ARC 必须 SSD
    或 NVMe。

  4. ZIL(SLOG)vs L2ARC 混淆:ZIL 是同步写日志加速(SSD 推荐),
    L2ARC 是读缓存扩展。不一样。

  5. swap 不要放 zvol 上:ZFS 的写时复制 + 内存压力时需要 swap →
    死锁。如果用 ZFS root,swap 单独建普通分区。

精确评价 共 0 人评价
可复现性
可复现 · 0 不可复现 · 0
文风
文风流畅 · 0 文风晦涩 · 0
立场
支持 · 0 反对 · 0

登录后即可对本帖作出评价。

评论区 0 条 · 所有人可在此交流

登录后参与评论。

还没有评论,来说两句。