LANUNION ARCHIVELANUNION蓝色札记

← 档案索引

ARCHIVE NO. 005

RAID 写惩罚与 IOPS 预算:一块盘掉线之后数据库为何先垮

CLASSIFICATIONINFRASTRUCTURE
DATE2026.09.16
状态 STATUSRESTRICTED
READING TIME7 MIN
REVISION2.1

现象:重建一开始,数据库先垮了

周三凌晨,一台跑着主库的物理机掉了一块盘。硬件 RAID 卡按策略自动开始重建, 一切看起来都在预期之内——直到两小时后,应用的慢查询告警开始堆积。

慢查询告警比磁盘告警晚了两个小时,这个时间差本身就说明问题: 真正的故障不是”掉盘”,而是重建把阵列的剩余能力吃掉了。 在阵列层面看,盘是掉了,但它掉得起;数据库垮掉的原因在别处。

重建期间的阵列读数

TERMINAL / PLAINTEXTLINE 01–06
# storcli64 /c0 /eall /sall show rebuild
---------------------------------------------------------
EID Slot   DID  State              DG/VD   Type  Size
---------------------------------------------------------
252   4    8    Rbld               0/0     SAS   3.638 TB
                    Rebuild Progress: 21%

重建进度 21%,预计还要六小时。同时看宿主机侧的块设备延迟:

TERMINAL / BASHLINE 01–03
iostat -x 5
# Device   r/s     w/s    rkB/s    wkB/s  await  %util
# sdb     1820     210   74560     3360   61.2   98.4

await 是 61.2 毫秒。这台机器的基线是 1.4 毫秒——慢了四十多倍, 而且 %util 顶到了 98.4%,队列是饱和的。数据库那边的现象就顺理成章了: 单次随机写要排队 60 毫秒,任何一条稍微复杂的写事务都会超时。

写惩罚:RAID 级别直接决定 IOPS 预算

要解释”为什么掉一块盘就撑不住”,得先把这台机器的 IOPS 预算算出来。

小随机写为什么贵

RAID 5 与 RAID 6 用校验块提供容错,而校验块是按条带计算的。 当应用发起一次小于条带宽度的随机写时,控制器没法只写这一块—— 它必须保证新的校验块与新的数据块一致,于是:

  1. 读旧数据块
  2. 读旧校验块
  3. 用两者算出新的校验块
  4. 写新数据块
  5. 写新校验块

两次读、两次写。控制器对外只看到 1 次逻辑写,磁盘上却发生了 4 次 I/O。 这个倍数就是写惩罚(write penalty)

RAID 10 是镜像,一次逻辑写只需要写两份副本,惩罚是 2。 RAID 6 有两份独立校验,写一次数据要更新两个校验块,惩罚是 6。 RAID 0 没有冗余,惩罚是 1。

读操作不受惩罚影响——读只需要命中一个块,无论哪个级别都是 1 次 I/O。 所以写惩罚只削写侧的预算,这也解释了为什么纯读的负载在 RAID 5 上跑得很好。

把预算算出来

IOPS可用=IOPS单盘×N写惩罚\mathrm{IOPS}_{\text{可用}} = \frac{\mathrm{IOPS}_{\text{单盘}} \times N}{\text{写惩罚}}

这台机器是 8 块 4 TB NL-SAS 7200 转,厂商给的随机 IOPS 参考值是每块 120。 裸盘合计 8×120=9608 \times 120 = 960 IOPS。按不同级别算下来:

级别写惩罚可用写 IOPS可用读 IOPS容错能力
RAID 01960960
RAID 102480960每组一块
RAID 5(7+1)4240960一块
RAID 6(6+2)6160960两块

这台机器用的是 RAID 5,可用写 IOPS 是 240

而出问题的这个库,业务高峰期的写负载实测在 180 IOPS 上下:

240180=1.33\frac{240}{180} = 1.33

只有 33% 的余量。 这个数字意味着,只要阵列的有效能力下降四分之一, 写路径就会开始排队。而重建期间掉四分之一是轻而易举的。

这台机器的结局在它上线那天就定了。重建只是那个把余量耗尽的触发器, 不是原因。任何一个把写负载压在 RAID 5 预算 75% 上的方案,都不该被批准。

重建窗口:4 TB 要读多久

重建的本质是把每一块幸存磁盘完整读一遍,重建出丢失那块盘上的数据。 所以重建耗时由容量与顺序读带宽决定,与写惩罚无关:

T重建=Cv顺序T_{\text{重建}} = \frac{C}{v_{\text{顺序}}}

4 TB 的盘,顺序读按 150 MB/s 算:

T=4×1012150×106=26667 s7 小时 24 分T = \frac{4 \times 10^{12}}{150 \times 10^{6}} = 26667\ \text{s} \approx 7\ \text{小时}\ 24\ \text{分}

七小时二十四分。这是在不限制重建速度、阵列全速给它让路的前提下。 实际生产里通常会给重建限速以保住业务,那个数字会难看很多:

重建限速预计耗时
不限速(150 MB/s)约 7 小时 24 分
100 MB/s约 11 小时 7 分
40 MB/s约 27 小时 47 分

限速越低,无冗余窗口越长。 这正是这件事最难的地方—— 它不是一个可以调优的参数,而是一个必须二选一的取舍。

重建期间没有冗余,也没有余量

两个风险同时压在重建的那几个小时里:

  • 没有冗余。 重建完成前,再掉任何一块盘就是整组数据丢失。 这也是为什么重建期间绝对不能主动重启、拔盘或做任何”顺手维护”。
  • 没有性能余量。 重建的顺序读会填满磁盘队列,把随机 I/O 的服务时间推高。 上文的 await 从 1.4 毫秒涨到 61.2 毫秒,就是这个机制的直接读数。

处理

先限速,保住业务

当时的首要目标是让数据库先活下来,所以把重建速度压下来:

TERMINAL / BASHLINE 01–05
# LSI/Broadcom 卡:把重建速率从 30% 降到 15%
storcli64 /c0 set rebuildrate=15

# Linux 软 RAID 的等价操作
echo 40000 | sudo tee /proc/sys/dev/raid/speed_limit_max

代价是把无冗余窗口从 7 小时拉长到十几小时。这是一次明确的取舍, 要和业务方一起做,不能由运维单方面决定——限速拖长的是数据完全暴露的时间。

重建期间不要做的事

  • 不要重启宿主机。 任何计划内重启都应当推迟到重建结束。
  • 不要跑备份或巡检。 那些任务会额外给同一组盘加压,直接把余量吃掉。
  • 不要在这个窗口内做任何存储侧的变更,包括扩容、改阵列、换卡。
  • 把监控阈值调紧。 重建期间应当盯着剩余磁盘的 media error 计数, 那个数字一旦开始涨,说明第二块盘也在路上了。

复盘:这台机器一开始就不该用 RAID 5

重建完成后做的第一件事是重新算了一遍这台机器的定位,结论很清楚:

  • 数据库不该建在 RAID 5 上。 有大量小随机写的负载,应当用 RAID 10—— 写惩罚 2 而不是 4,可用写 IOPS 直接翻倍到 480。 代价是只能拿到一半裸容量,对一块 4 TB 的盘来说这个代价完全值得。
  • 选购时的算账方式错了。 当时按”bare capacity 除以盘数”挑的级别, 算的是容量账,没算 IOPS 账。正确的顺序是先算负载的读写 IOPS 需求, 再倒推需要什么级别、多少块盘。
  • 重建时间是选型指标,不是运维指标。 单盘容量翻倍时, 重建时间也翻倍,而无冗余窗口的长度直接等于可用性风险。 盘越大越该用 RAID 10,这个反直觉的结论也是从这张表里读出来的。
  • 别把”能跑”当成”有余量”。 上线时压测跑到 75% 就通过了, 而生产环境的余量应当按故障态来留——正常态的余量不能当作依据。

一条可以固化的巡检项:每个阵列都要有明确的写 IOPS 预算与当前占用率, 占用率超过 50% 的就该进入扩容计划。这个数字不用很精确, 但它能把”掉一块盘就雪崩”这类问题在掉盘之前就暴露出来。