SStorpath
原理预计 35 分钟

磁盘 I/O:IOPS、吞吐与延迟的三角关系

为什么队列深度一加大 IOPS 就上去、延迟也跟着上去?这节课把 iostat 每一列讲透。

学完这节你能做到

  • 推导 IOPS × 块大小 = 吞吐,并解释它何时不成立
  • 逐列读懂 iostat -x 输出,判断磁盘是否饱和
  • 区分 HDD / SATA SSD / NVMe 的性能量级与适用场景

一次 I/O 都经过了什么

应用调用 write() 之后,数据并不会立刻躺在盘片或闪存颗粒上。它要穿过一串组件:

应用 write()
  └─ 系统调用 → VFS
       └─ 文件系统(XFS / ext4)
            └─ 页缓存(脏页,稍后回写)
                 └─ 块层(bio → request,合并 + 调度)
                      └─ 设备驱动(NVMe / SCSI)
                           └─ 设备内部(控制器、缓存、介质)

这条链上每一层都可能成为瓶颈,而且每一层看到的"一次 I/O"都不一样:应用写 4KB,文件系统可能因为日志变成两次写,块层可能把相邻的几个请求合并成一个,SSD 内部还可能因为垃圾回收把它放大成几倍的实际擦写。

i为什么 write() 返回很快

默认情况下 write() 只是把数据放进页缓存就返回了,真正落盘由内核后台回写。只有 fsync()(或以 O_DIRECT / O_SYNC 打开)才会等待设备确认。数据库和存储服务大量使用 fsync,所以它们对盘的写延迟极其敏感——这也是存储节点必须选带掉电保护的企业盘的原因。

四个量之间的关系

上一节已经建立了 吞吐 ≈ IOPS × 块大小。现在补上第四个量——并发,它才是解释"为什么延迟和 IOPS 会同时上升"的关键。

排队论里有个 Little's Law,落到存储上可以这么记:

并发数(队列深度) ≈ IOPS × 延迟

举个例子:一块盘单请求延迟 100µs。

  • 队列深度 1:每秒最多 1 / 0.0001 = 10000 IOPS,延迟 100µs
  • 队列深度 32:设备内部并行处理,IOPS 可以涨到 30 万,但每个请求的延迟会升到 100µs 以上

加大队列深度换来吞吐,代价是延迟。 所以压测报告里只写 IOPS 不写队列深度和延迟,等于什么都没说。

!利用率逼近 100% 时,延迟不是线性上升

排队系统的响应时间大致正比于 1 / (1 - 利用率)。利用率从 50% 涨到 60%,延迟变化很小;从 90% 涨到 95%,延迟直接翻倍。这就是为什么容量规划要给磁盘留水位——不只是怕写满,更是怕延迟崩掉。

iostat -x 逐列精读

这是存储工程师看得最多的一屏。以 iostat -xz 1 为例:

Device  r/s     w/s    rkB/s     wkB/s   rrqm/s wrqm/s  %rrqm %wrqm r_await w_await aqu-sz rareq-sz wareq-sz  %util
nvme0n1 12043   3210   770752   205440    0.00   1.20   0.00  0.04    0.31    0.45   4.83    64.00    64.00  99.20

逐列拆开:

含义怎么用
r/s w/s每秒读/写次数,即 IOPS和业务预期对得上吗
rkB/s wkB/s每秒读/写数据量除以 IOPS 就是平均块大小
rrqm/s wrqm/s被块层合并掉的请求数合并率高说明是顺序 I/O
r_await w_await请求平均耗时,含排队时间最重要的一列
aqu-sz平均队列长度饱和度信号,持续大于 1 说明在排队
rareq-sz wareq-sz平均请求大小(KB)判断随机小 I/O 还是顺序大 I/O
%util有 I/O 在处理的时间占比对 NVMe 已失真,仅供参考

读这一屏的顺序建议是:先看 await 判断快不快 → 再看 aqu-sz 判断是不是在排队 → 再看 req-sz 判断负载类型 → 最后才看 %util

×%util 在 SSD 时代已经不可信

%util 的定义来自机械盘时代:磁头一次只能服务一个请求,所以"有请求在处理的时间比例"就等于繁忙程度。NVMe 有几十上百个硬件队列,能同时处理大量请求,因此只要队列里一直有活,%util 就是 100%——它此时只能说明"盘没闲着",完全不能说明"盘扛不住了"。

介质的量级要刻进脑子

规划和排障时,你需要对数量级有直觉。以下是单盘的典型值(4KB 随机读):

介质IOPS 量级顺序吞吐典型延迟每 TB 成本
7.2K SATA HDD100 ~ 200150 ~ 250 MB/s5 ~ 15 ms最低
SATA SSD5 万 ~ 10 万500 MB/s(受限于 SATA)100 ~ 300 µs
NVMe TLC SSD40 万 ~ 100 万3 ~ 7 GB/s50 ~ 150 µs较高

关键差距是随机 IOPS 差了三个数量级。这解释了很多设计决策:

  • 为什么 Ceph 的元数据池、GPFS 的 metadata 一定要放 SSD——元数据操作全是小随机 I/O
  • 为什么 HDD 集群做 EC 重建会那么慢——重建是大量随机读
  • 为什么"混闪"方案要把 WAL/DB 放到 NVMe 上
估算先用单盘乘法,再打折

规划时先算理论值:集群 IOPS ≈ 单盘 IOPS × 盘数 × 冗余折扣。然后按经验打 60% ~ 70% 的折——网络、CPU、软件栈、PG 分布不均都会吃掉一部分。算出来的数字用来判断"够不够",而不是用来承诺 SLA。

检查点单选

一块盘的 iostat 显示 r/s = 8000,rkB/s = 512000。这个负载的平均读请求大小是多少?

检查点单选

某 NVMe 盘 w_await 从 0.2ms 涨到 15ms,同时 aqu-sz 从 1 涨到 120,wareq-sz 保持 4KB 不变。最可能的原因是?

动手:用 elbencho 亲手验证一遍

理论看完,一定要自己跑一次。找一块空闲的测试盘(不要在生产盘上跑写测试):

# 4KB 随机读,队列深度从 1 慢慢加,观察 IOPS 与延迟怎么变
elbencho -r --direct -b 4k -t 1 --iodepth 1 --rand --randamount 10g \
         -s 100g --timelimit 30 --lat --latpercent /dev/nvme0n1

# 把 --iodepth 换成 4、16、64 各跑一次,记录三组数字:
#   IOPS、平均延迟、P99 延迟

把三组数字画成表,你会亲眼看到那条曲线:IOPS 先快速上升然后趋于平缓,而延迟在 IOPS 见顶之后开始陡增。那个拐点就是这块盘的可用工作区间上限。

!--direct 必须加

不加 --direct,数据会走页缓存,你测的就是内存速度而不是盘速度。看到"HDD 跑出 3GB/s"这种结果,八成就是忘了这个参数。

这节课的落点

  • 一次 I/O 要穿过 VFS、文件系统、页缓存、块层、驱动、设备,每层都可能放大或合并
  • 记住两个式子:吞吐 = IOPS × 块大小队列深度 = IOPS × 延迟
  • 读 iostat 的顺序:await → aqu-sz → req-sz → %util
  • HDD 与 NVMe 的随机 IOPS 差三个数量级,这决定了绝大多数架构选择

延伸资料

  • ·Systems Performance, 2nd Edition — Brendan Gregg
  • ·k8s-in-actionk8s/etcd-disk-performance.md