企业级 SSD 性能评测:稳态、尾延迟与 fio 实测命令

目录

[1. 测什么](#1. 测什么)

[2. 判据、样本量、条件](#2. 判据、样本量、条件)

[3. 测试前准备](#3. 测试前准备)

[4. 公共参数](#4. 公共参数)

[5. 与负载无关的预处理](#5. 与负载无关的预处理)

[6. 稳态预处理和正式测量](#6. 稳态预处理和正式测量)

S1:顺序带宽

[R1/R2:4 KiB 随机读](#R1/R2:4 KiB 随机读)

[W1:4 KiB 随机写](#W1:4 KiB 随机写)

[M1:4 KiB 70/30](#M1:4 KiB 70/30)

[M2:70% 饱和 IOPS 的 QoS](#M2:70% 饱和 IOPS 的 QoS)

[L1:6 小时一致性](#L1:6 小时一致性)

[7. 稳态计算](#7. 稳态计算)

[8. 温度复测和收尾](#8. 温度复测和收尾)

[9. 往期相关文章](#9. 往期相关文章)


1. 测什么

固定对比点只有这一组,所有盘用同一份作业:

编号 负载 正式结果
S1 128 KiB 顺序读、顺序写 稳态带宽
R1 4 KiB 随机读,OIO=1 平均延迟、P99.9
R2 4 KiB 随机读,扫到饱和 饱和 IOPS;OIO=1 与饱和点的尾延迟
W1 4 KiB 随机写,扫到饱和 稳态 IOPS 和尾延迟
M1 4 KiB 70/30 随机混合 稳态 IOPS;读、写分开的 P99.9、P99.99
M2 同上,限速到饱和 IOPS 的 70% 业务强度下的 QoS,不看饱和延迟
L1 M1 连续 6 小时,1 秒采样 周期性 GC 毛刺

需要时再加 8/16 KiB、50/50、24 小时,不能替换上表。纯读表示没有并发写;写干扰下的读能力只看 M1/M2 的读分项。

每个随机点在测量前,必须先用同块大小、同读写比例的写负载进入稳态。128 KiB 顺序写满两遍只消除空盘,不能代替这一步。

2. 判据、样本量、条件

判据在测试前写入,不接受测完再挑选好看的曲线。规格书写明稳态值时,同块大小、同读写比、同 OIO、同温度下不低于该值;只写 "up to" 或空盘峰值的,不当验收线。

项目 判据
S1 稳态带宽 ≥ 规格书稳态值
W1、R2 稳态 IOPS ≥ 规格书对应条款
R1 平均 ≤ X µs,P99.9 ≤ Y µs,X/Y 来自规格或业务
M2 读 P99.9 ≤ Y,读 P99.99 ≤ Z;写分项单独设限
一致性 测量窗口内 1 秒 IOPS,(max⁡−min⁡)/avg≤10%(max−min)/avg≤10%,且无单向漂移
L1 不出现超出 SLO 的周期性崩塌;Max 单独列出
盘间 最差片满足以上条款

性能样本每型号至少 3 片,主推型号 5 片,报中位数和最差片。P99.99 每片每个点至少累计约 100 万次 IO,P99.999 需要千万到上亿次;3 片各跑 10 秒不能合成一个尾延迟。这 3 片不能证明 JESD218 的 UBER 或 TBW。

报告必须写明:型号、容量、固件、OP、扇区格式、写缓存和 APST 状态、预处理与稳态判据、块大小、读写比、numjobs × iodepth、是否限速、引擎、内核、PCIe 代际和车道、调度器、CPU 绑定、进风和盘温、是否热节流。条件变化就是一次新测试。全 LBA、IO 对齐、数据不可压缩。不要把 buffer_compress_percentage=0 写进命令,那表示数据完全可压缩。

3. 测试前准备

把 DEV 改成测试盘。下面会关闭 APST、切换调度器并安全擦除。

复制代码
DEV=/dev/nvme0n1
NS=1
OUT=/root/ssd-perf/$(date +%F-%H%M)
mkdir -p "$OUT"
test -b "$DEV" || { echo "不是块设备"; exit 1; }
findmnt -S "$DEV" && { echo "已挂载,停止"; exit 1; }
command -v fio >/dev/null && command -v nvme >/dev/null
记录身份和链路,不改变盘
nvme id-ctrl "$DEV" | tee "$OUT/id-ctrl.txt"
nvme id-ns "$DEV" | tee "$OUT/id-ns.txt"
nvme smart-log "$DEV" | tee "$OUT/smart-before.txt"
nvme get-feature "$DEV" -f 0x06 -H | tee "$OUT/vwc.txt"     # 易失写缓存
nvme get-feature "$DEV" -f 0x0c -H | tee "$OUT/apst-before.txt"
lspci -vv -s "$(basename "$(readlink -f /sys/class/nvme/$(cat /sys/class/block/$(basename "$DEV")/device/device 2>/dev/null)/device 2>/dev/null || true)")" 
> "$OUT/lspci.txt" 2>/dev/null || lspci -vv | tee "$OUT/lspci-all.txt"
cat /sys/block/$(basename "$DEV")/queue/scheduler | tee "$OUT/scheduler-before.txt"
随机读不要预读;NVMe 测试用 none 调度器
echo 0 > /sys/block/$(basename "$DEV")/queue/read_ahead_kb
echo none > /sys/block/$(basename "$DEV")/queue/scheduler
延迟测试期间关闭 APST,结束后恢复
nvme set-feature "$DEV" -f 0x0c -v 0
nvme format "$DEV" -n "$NS" -s 1

nvme format -s 1 会销毁该命名空间。加密盘若改用 -s 2,报告里要注明,因为它和块擦除后的初始状态可能不同。fio 使用 3.25 及以上。libaio 与 io_uring 分两套结果,基线只用 libaio。

4 KiB 随机写大约每 10 万 IOPS 消耗约 33 TB/天,预处理可能持续数小时。测试盘要计入寿命消耗,不能拿必须保留数据的生产盘来做。

4. 公共参数

后续命令都依赖这些定义。Outstanding IO = numjobs × iodepth;限速测试固定 numjobs=1,因为 rate_iops 是按 job 生效的。

复制代码
COMMON=(
  --filename="$DEV"
  --direct=1
  --ioengine=libaio
  --thread=1
  --group_reporting=1
  --norandommap=1
  --randrepeat=0
  --random_generator=lfsr
  --scramble_buffers=1
  --clat_percentiles=1
  --percentile_list=50:90:99:99.9:99.99:99.999
  --log_avg_msec=1000
  --log_unix_epoch=1
  --per_job_logs=0
  --output-format=normal,json
)

fio 3.x 的 latency log 数值单位是纳秒。IOPS 日志四列分别是毫秒时间戳、IOPS、方向(0 读、1 写)、块大小。

5. 与负载无关的预处理

全盘 128 KiB 顺序写两遍。容量取命名空间大小,不按 NAND 裸容量计算。

复制代码
fio --name=seq_fill_2x "${COMMON[@]}" \
  --rw=write --bs=128k --iodepth=32 --numjobs=1 \
  --size=100% --loops=2 \
  --write_bw_log="$OUT/seq_fill" \
  --output="$OUT/seq_fill.txt"

6. 稳态预处理和正式测量

每个负载先跑预处理,再用同一终端立刻跑测量,中间不能停。预处理默认 4 小时;窗口未收敛就延长,不能提前取空盘或下降段的数字。

稳态的工程判据:最后 300 个 1 秒写 IOPS 样本,极差不超过均值的 10%,且没有持续上升或下降。这是单负载验收规则,不是 SNIA 原文里"5 个 Round、极差 20%"的完整 PTS 矩阵,两者结果不能横比。

复制代码
# 用法: precondition_write <name> <bs> <rwmixread或空> <runtime秒>
precondition_write() {
  local name=$1 bs=$2 mix=$3 runtime=$4
  local extra=()
  [[ -n $mix ]] && extra+=(--rw=randrw --rwmixread="$mix") || extra+=(--rw=randwrite)
  fio --name="pre_${name}" "${COMMON[@]}" \
    "${extra[@]}" --bs="$bs" --iodepth=32 --numjobs=4 \
    --time_based=1 --runtime="$runtime" \
    --write_iops_log="$OUT/pre_${name}" \
    --output="$OUT/pre_${name}.txt"
}
用法: measure <name> <rw> <bs> <iodepth> <numjobs> <seconds> [rwmixread] [rate_iops]
measure() {
local name=$1 rw=$2 bs=$3 qd=$4 jobs=$5 runtime=$6 mix=${7:-} rate=${8:-}
local extra=()
[[ -n $mix ]] && extra+=(--rwmixread="$mix")
[[ -n $rate ]] && extra+=(--rate_iops="$rate" --rate_process=poisson)
fio --name="$name" "${COMMON[@]}" 

--rw="$rw" --bs="$bs" --iodepth="$qd" --numjobs="$jobs" 

"${extra[@]}" --time_based=1 --runtime="$runtime" 

--write_iops_log="$OUT/${name}" 

--write_lat_log="$OUT/${name}" 

--write_bw_log="$OUT/${name}" 

--write_hist_log="$OUT/${name}" --log_hist_msec=1000 

--output="$OUT/${name}.txt"
}

S1:顺序带宽

顺序写在两遍写满后直接测量。顺序读放在顺序写之后立刻执行。各采 10 分钟。

复制代码
measure seqwrite write 128k 32 1 600
measure seqread  read  128k 32 1 600

R1/R2:4 KiB 随机读

先用 4 KiB 随机写进入稳态,再立刻测读。QD 扫描用单 job,OIO 才等于 iodepth。

复制代码
precondition_write 4k_for_read 4k "" 14400
measure randread_qd1 randread 4k 1 1 600
for qd in 1 4 16 32 64 128 256; do
measure "randread_qd${qd}" randread 4k "$qd" 1 600
done

饱和点取 IOPS 不再明显上升、尾延迟仍可接受的最大 QD。单核提交能力不够时,另跑一组 numjobs=4 --iodepth=32,报告里写明 OIO=128,不能把它标成 QD=32。

W1:4 KiB 随机写

预处理和测量都是随机写。测量窗口 10 分钟;若预处理日志尚未满足 10% 极差,继续预处理。

复制代码
precondition_write 4k_write 4k "" 14400
measure randwrite_qd1 randwrite 4k 1 1 600
for qd in 4 16 32 64 128 256; do
  measure "randwrite_qd${qd}" randwrite 4k "$qd" 1 600
done

M1:4 KiB 70/30

必须重新预处理,不能沿用纯写稳态。

复制代码
precondition_write 4k_7030 4k 70 14400
measure randrw7030_qd1 randrw 4k 1 1 600 70
for qd in 4 16 32 64 128 256; do
  measure "randrw7030_qd${qd}" randrw 4k "$qd" 1 600 70
done

文本结果里的 read: 和 write: 分开记录。只报混合总 IOPS 或一个平均延迟,本项无效。

M2:70% 饱和 IOPS 的 QoS

先从上一步得到稳态饱和总 IOPS,再乘 0.7,且只用一个 job。例如饱和为 100000,则限速 70000。iodepth 要给够,保证限速生效;若实际 IOPS 明显低于设定值,增大 QD 后重测。

复制代码
measure randrw7030_qos70 randrw 4k 64 1 600 70 70000

这一项至少 10 分钟。P99.99 若 IO 数不足,按实际 IOPS 延长,不把时间缩短后再宣称该分位数。

L1:6 小时一致性

用 M1 已确认的饱和 QD,不限速,连续运行。

复制代码
measure randrw7030_6h randrw 4k 32 4 21600 70

关键业务把 21600 改为 86400。每秒 IOPS 和直方图都保留。

50/50 只在业务需要时追加,预处理比例也必须是 50:

复制代码
precondition_write 4k_5050 4k 50 14400
measure randrw5050 randrw 4k 32 4 600 50

7. 稳态计算

对预处理日志的写方向取最后 300 秒。range_ratio 大于 0.10,或 slope_ratio 绝对值大于约 0.10,都视为未稳态。

复制代码
python3 - <<'PY' "$OUT/pre_4k_write_iops.1.log"
import math, sys
vals=[]
for line in open(sys.argv[1]):
    t,v,d,bs=line.split(',')[:4]
    if int(d)==1:
        vals.append(float(v))
w=vals[-300:]
if len(w)<300:
    sys.exit("样本不足 300 秒")
n=len(w); avg=sum(w)/n
xs=range(n)
xavg=(n-1)/2
den=sum((x-xavg)**2 for x in xs)
slope=sum((x-xavg)*(y-avg) for x,y in zip(xs,w))/den
print(f"avg={avg:.0f}")
print(f"range_ratio={(max(w)-min(w))/avg:.3f}")
print(f"slope_ratio={slope*n/avg:.3f}")
PY

正式点同样用这个窗口检查。通过后,带宽和 IOPS 取窗口均值;延迟取 fio 输出里对应方向的 completion latency,不用平均延迟代替 P99.9。

8. 温度复测和收尾

在规格书的上限工作温度和规定风量下,复测 S1、R2 饱和点、W1 饱和点和 M2。记录 SMART 温度。已经热节流时,先按风量和进风判定热设计,不直接把该点写成介质性能。40°C、3 个月是 JESD218 的掉电保持条件,不是性能环境。

复制代码
nvme smart-log "$DEV" | tee "$OUT/smart-after.txt"
nvme set-feature "$DEV" -f 0x0c -v "$(awk '/Current Value/{print $NF}' "$OUT/apst-before.txt")"
echo mq-deadline > /sys/block/$(basename "$DEV")/queue/scheduler 2>/dev/null || true

APST 的恢复值以 get-feature 的原始值为准,不能盲目写回一个猜出来的数字。

性能结论只采用:本负载预处理已通过稳态窗口、全盘、不可压缩数据、条件完整的结果。空盘峰值、短于窗口的尾延迟、经过文件系统缓存或只测到部分 LBA 的结果,可以留档,但不能作为企业级性能结论。

9. 往期相关文章

更多关于 SSD 性能测试与 FIO 实战的内容,欢迎阅读我的 CSDN 往期文章:

相关推荐
工作10年+,存储芯片行业3 小时前
企业级 SSD 耐久度评测(JESD218 / JESD219 + FIO)
网络·ssd·nvme·存储·pcie·nand·fio
工作10年+,存储芯片行业21 小时前
HTOL 实战:原理、流程、场景与解析
ssd·nvme·存储·pcie·nand·htol·qlc
gwf2161 天前
SSD I/O路径全栈优化与Linux内核存储栈性能调优实战
性能优化·linux内核·nvme·性能调优·固态硬盘·存储技术·io_uring
工作10年+,存储芯片行业2 天前
浮栅晶体管极性说明
ssd·nvme·芯片·存储·pcie·nand·mos
工作10年+,存储芯片行业2 天前
NVMe Surprise 掉链、控制器实例漂移与残留 I/O
ssd·nvme·存储·pcie·nand·csts·掉链
工作10年+,存储芯片行业4 天前
SSD 端到端数据保护:PI 三件套深度解析
ssd·nvme·芯片·存储·pcie·保护·nand
工作10年+,存储芯片行业5 天前
Linux NVMe 中断排查与性能优化:CPU 亲和性
linux·运维·服务器·windows·性能优化·ssd·pcie
Felven6 天前
stress-ng 与 fio 性能测试工具使用指南
linux·测试工具·fio·stress-ng
gwf2166 天前
NAND闪存晶圆级测试与表征技术深度解析(从CP测试到可靠性认证全流程)
晶圆测试·3d nand·nand·失效分析·可靠性测试·nand闪存·cp测试