TT Lab
开始
学习 学习路径 课程

存储实务 — RAID、快照、iSCSI、fio

把“磁盘很慢”变成数字 — IOPS、延迟、队列深度与 fio

在 TT Lab 中继续学习

一句话总结

磁盘性能不是一个数字,而是三个:每秒多少次(IOPS)、每秒多少数据量(吞吐量)、一次要多久(延迟)。这三者会随块大小和队列深度相互变化,所以“能跑到多少 IOPS”如果不同时说明条件,就毫无意义。fio 就是把这些条件固定下来再去测量的工具。

为什么需要它

“存储很慢”这类报告大多没有证据。在购买新存储之前、迁移 LUN 之前、选择云卷规格之前,必须有在相同条件下测得的数字才能比较。而用 dd if=/dev/zero of=파일(占位符为文件名)测出的数字几乎总是错的。写入会先进入页缓存,记录下来的是内存速度;又因为它每次只顺序写一份,测的是与数据库实际所做的小块随机读完全不同的事情。

工作原理

三个数字的关系。 吞吐量 = IOPS × 块大小。4KiB 块下 10,000 IOPS 约为 40MiB/s,1MiB 块下 400 IOPS 为 400MiB/s。数据库的随机读是小块,所以 IOPS 和延迟重要;备份或大文件复制是大块,所以吞吐量重要。因此测量要先确定“要模拟什么”。

队列深度与利特尔法则。 同时在途的请求数就是队列深度(queue depth)。排队论中的利特尔法则(Little's law)是“系统内平均停留的个数 = 处理率 × 平均停留时间”。套用到存储上就是 并发请求数 ≈ IOPS × 平均延迟。队列深度为 1 时,IOPS 不可能超过 1 ÷ 延迟。延迟为 0.2ms 时最多 5,000 IOPS。提高队列深度后,设备会重叠处理多个请求,IOPS 上升,但每个请求都要排队,延迟也随之上升。存储规格书上的高 IOPS 多半是在深队列下测得的值,而每次只等一个请求的应用永远看不到这个数字。

看百分位数,而不是平均值。 平均延迟是 1ms,但如果每 100 次里有一次耗时 50ms,那么每个请求都要多次读磁盘的服务就会经常踩到这条尾部。所以要同时看 p99(第 99 百分位)延迟。

fio 的选项。 fio 文档中的选项里,真正左右结果的只有几个。

选项 含义 本实验的值
rw 读/写,顺序/随机 randread
bs 块大小 4k
ioengine 发出请求的方式 libaio(异步,队列深度才有意义)
iodepth 队列深度 1 和 32
direct 绕过页缓存 1
runtime、time_based 在设定的时间内持续运行 10 秒
output-format 输出格式 json

direct=1 以 O_DIRECT 方式打开文件,跳过页缓存。去掉它,从第二次读取开始答案就来自内存,测的就是 RAM 而不是磁盘。如果用的不是 ioengine=libaio,而是同步方式(psync),那么即使调高 iodepth,也只会每次发出一个请求,无法做队列深度实验。

如何读取 JSON。 --output-format=json 结果的 jobs[0].read 下面有 iops、延迟统计 lat_ns(平均值为 mean)、完成延迟 clat_ns.percentile 的 "99.000000" 键。单位是纳秒,所以换算成微秒时要除以 1,000。不要复制供人阅读的默认输出,而要从 JSON 中取数字,这样报告和复现才能一致。

在现场相遇的样子

云块存储卷通常按规格出售 IOPS 和吞吐量上限。这些上限只有在足够深的队列下才能达到。如果应用以队列深度 1 做同步写入(例如每个事务都 fsync 的数据库日志),即使买了昂贵的规格,也只能跑出由延迟决定的水平。这时要看的数字不是 IOPS,而是延迟。

引入新存储的测试中,先定好两三个模拟生产负载的条件,并始终用同一条命令测量。把命令和 JSON 结果一起保存,半年后有人说“比以前慢了”,就可以重新运行同一条命令,用数字回答。测量要在生产环境文件系统上的专用文件中进行;在裸设备上做写入测量会清除数据,所以只在空设备上做。另外,测量期间在旁边开着 iostat -x,就能同时确认 fio 的数字与内核看到的设备数字是否一致。

下一项实验要做什么

在同一台 VM 内搭建 LIO 目标端,提供一个 512MiB 的 LUN,再用发起端执行发现和登录,确认新磁盘以 iscsi 显示。将其格式化为 ext4,用带 _netdev 的 fstab 行挂载,并开启自动登录。然后在其上的专用文件中以队列深度 1 和 32 测量 4KiB 随机读,从 JSON 中取出 IOPS、p99、平均延迟,确认利特尔法则成立。