2000 元的机械硬盘 > 3000 元的固态硬盘?

A.K.A. 镜像站 ZFS 调优实践


iBug @ USTC

2024 年 8 月 17 日
南京大学 开源软件论坛

USTC Mirrors

  • 日均服务量:(2024-05 ~ 2024-06)
    • 出流量 ~36 TiB
    • HTTP 请求数 17M,响应流量 19 TiB
    • Rsync 请求数 147.8K(21.8K),输出流量 10.3 TiB
  • 极限情况的仓库容量:
    • HTTP 服务器(XFS):63.3 TiB / 66.0 TiB (96%, 2023-12-18)
    • Rsync 服务器(ZFS):42.4 TiB / 43.2 TiB (98%, 2023-11-21)

背景

  • HTTP 服务器:
    • 2020 年下半年搭建
    • 10 TB × 12
    • 2 TB × 1
    • XFS on LVM on HW RAID
    • 考虑到 XFS 不能缩,VG 留了 free PE
  • Rsync 服务器:
    • 2016 年下半年搭建
    • 6 TB × 12
    • 240 GB × 2 + 480 GB × 1 (Optane 900p)
    • RAID-Z3(8 data + 3 parity + 1 hot spare)
    • 全默认参数(除了 zfs_arc_max

硬盘 I/O 日常 > 90%,校内下载 iso 不足 50 MB/s

USTC 镜像站两台服务器在 2024 年 5 月期间的磁盘负载

ZFS

  • 单机存储的终极解决方案
  • 集 RAID、LVM、FS 于一体
  • 所有数据都有 checksum
  • Fire and forget
  • 好多参数啊

前期学习与实验

  • 从(另一个)老师那嫖了点盘装上了 ZFS,用于 研 究 学 习
  • I/O 负载来源?上 PT 站
  • 练习时长两年半的成果: 1.20 PiB, 1.83 TiB

重要学习资料:

好像给什么奇怪的东西加入了 Grafana

镜像站

  • 提供文件下载服务
  • 也提供「家庭宽带上下行流量比例平衡」服务
  • 读多写少,并且几乎所有操作都是整个文件顺序读写
  • 少量的数据损坏没啥不良后果

2024 年 8 月 USTC 镜像仓库内的文件大小分布
其中中位数为 9.83 KiB,平均大小为 1.60 MiB

重建 Rsync 服务器

  • RAID-Z3 的 overhead 较高,而且拆成两组 RAID-Z2 = 两倍的 IOPS
  • 镜像站调优计划:
    • recordsize=1M:反正都是全文件顺序读
    • compression=zstd:至少可以压掉 > 1M 文件的 padding
      • OpenZFS 2.2 将 early abort 机制推广到了 Zstd 3+,不必担心性能问题
    • xattr=off:谁家镜像需要 xattr?
    • atime=off, setuid=off, exec=off, devices=off:开着干啥?
    • secondarycache=metadata:Rsync 就别来消耗固态寿命了
  • Danger Zone:
    • sync=disabled:囤到 zfs_txg_timeout 再写盘
    • redundant_metadata=some:偶尔坏个文件也没事
  • Full version: LUG @ USTC Documentation

ZFS 参数

  • 290+ 参数不能个个都学习嘛(感谢 Aron Xu @ BFSU)
  • ARC 容量:
    
    # Set ARC size to 160-200 GiB, keep 16 GiB free for OS
    options zfs zfs_arc_max=214748364800
    options zfs zfs_arc_min=171798691840
    options zfs zfs_arc_sys_free=17179869184
            
  • ARC 内容:
    
    # Favor metadata to data by 20x (OpenZFS 2.2+)
    options zfs zfs_arc_meta_balance=2000
    
    # Allow up to 80% of ARC to be used for dnodes
    options zfs zfs_arc_dnode_limit_percent=80
            
  • I/O 队列深度:
    
    # See man page section "ZFS I/O Scheduler"
    options zfs zfs_vdev_async_read_max_active=8
    options zfs zfs_vdev_async_read_min_active=2
    options zfs zfs_vdev_scrub_max_active=5
    options zfs zfs_vdev_max_active=20000
            
  • Full version: LUG @ USTC Documentation

重建成果

  • 略感惊喜的压缩率:39.5T / 37.1T = 1.07x
    • 正确用法:zfs list -po name,logicalused,used
    • 实际压缩率:1 + 6.57%(-2.67 TB / -2.43 TiB)
    • 等于删了 9 份微信数据
  • 合理的磁盘 I/O

重建前后 Rsync 服务器的磁盘负载与空闲空间比较

HTTP 服务器

  • 硬件 RAID + LVM + XFS + Kernel page cache(开箱即用?)
  • SSD?LVMCache!
    • 1M extents? Block size? Algorithm?
    • GRUB2
    • "oldssd"
  • XFS 不能缩,所以 VG 和 FS 两层都要留空间备用

重建前 HTTP 服务器采用的 LVMcache 方案的命中率

如法炮制

  • 体验一下更加先进的 kernel:6.8.8-3-pve(无需 DKMS 哦)
  • 重建为两组 RAID-Z2,开压缩
    • 面向 HTTP 用户的服务器,所以 secondarycache=all(放着不动)
    • 更好的 CPU,所以 compression=zstd-8
  • 更快的 zfs send -Lcp:36 小时倒完 50+ TiB 仓库
  • 压缩率:1 + 3.93%(-2.42 TB / -2.20 TiB)

重建前后两台服务器的磁盘负载比较
左边为重建前,中间为仅 Rsync 服务器重建后,右边为两台服务器均重建后的负载

两台服务器的 ZFS ARC 命中率

两台服务器重建后稳定的磁盘利用率

杂项

ZFS 压缩率

NAME LUSED USED RATIO
pool0/repo/crates.io-index 2.19G 1.65G 3.01x
pool0/repo/elpa 3.35G 2.32G 1.67x
pool0/repo/rfc 4.37G 3.01G 1.56x
pool0/repo/debian-cdimage 1.58T 1.04T 1.54x
pool0/repo/tldp 4.89G 3.78G 1.48x
pool0/repo/loongnix 438G 332G 1.34x
pool0/repo/rosdistro 32.2M 26.6M 1.31x

我数学不好: openzfs/zfs#7639

ZFS 压缩量

NAME LUSED USED DIFF
pool0/repo 58.3T 56.1T 2.2T
pool0/repo/debian-cdimage 1.6T 1.0T 549.6G
pool0/repo/opensuse 2.5T 2.3T 279.7G
pool0/repo/turnkeylinux 1.2T 1.0T 155.2G
pool0/repo/loongnix 438.2G 331.9G 106.3G
pool0/repo/alpine 3.0T 2.9T 103.9G
pool0/repo/openwrt 1.8T 1.7T 70.0G

Grafana I/O 统计


SELECT
  non_negative_derivative(sum("reads"), 1s) AS "read",
  non_negative_derivative(sum("writes"), 1s) AS "write"
FROM (
  SELECT
    first("reads") AS "reads",
    first("writes") AS "writes"
  FROM "zfs_pool"
  WHERE ("host" = 'taokystrong' AND "pool" = 'pool0') AND $timeFilter
  GROUP BY time($interval), "host"::tag, "pool"::tag, "dataset"::tag fill(null)
)
WHERE $timeFilter
GROUP BY time($interval), "pool"::tag fill(linear)

跑得有点慢(毕竟要先 GROUP BY 每个 ZFS dataset 再一起 sum

I/O 带宽:把里层的 readswrites 换成 nreadnwritten 即可

  • 如何用机械盘跑出平均 15K、最高 50K 的 IOPS?
  • 把 ARC hit 算进去

灵车时间

Proxmox Kernel


dpkg-divert --package lxc-pve --rename --divert /usr/share/apparmor-features/features.stock --add /usr/share/apparmor-features/features
wget -O /usr/share/apparmor-features/features https://github.com/proxmox/lxc/raw/master/debian/features
    

ZeroTier 仓库中一眼重复的内容

Dedup!

zfs create -o dedup=on pool0/repo/zerotier

# zdb -DDD pool0
dedup = 4.93, compress = 1.23, copies = 1.00, dedup * compress / copies = 6.04
    

效果倒是不错,但是不想像 ZFS dedup 这么灵怎么办?

jdupes


# post-sync.sh
# Do file-level deduplication for select repos
case "$NAME" in
  docker-ce|influxdata|nginx|openresty|proxmox|salt|tailscale|zerotier)
    jdupes -L -Q -r -q "$DIR" ;;
esac
    

jdupes 效果

Name Orig Dedup Diff Ratio
proxmox 395.4G 162.6G 232.9G 2.43x
docker-ce 539.6G 318.2G 221.4G 1.70x
influxdata 248.4G 54.8G 193.6G 4.54x
salt 139.0G 87.2G 51.9G 1.59x
nginx 94.9G 59.7G 35.2G 1.59x
zerotier 29.8G 6.1G 23.7G 4.88x
mysql-repo 647.8G 632.5G 15.2G 1.02x
openresty 65.1G 53.4G 11.7G 1.22x
tailscale 17.9G 9.0G 9.0G 2.00x

只要 ZFS 用得好

  • 妈妈再也不用担心我的硬盘分区
  • 机械硬盘 比西方的固态硬盘跑得还快
  • 成为第一个不再羡慕 TUNA 全闪的镜像站
  • 免费的额外容量
    • Dedup 会员红包
  • 碎片率?

谢谢!

本页面的链接: ibug.io/p/72

友情链接:2023 年南京大学报告: ibug.io/p/59