HOOOS

如何精准测试 SSD 和 RocksDB 的物理写放大(WAF)?从 Fio 到 db_bench 的实操指南

0 14 存储架构师 RocksDBFio写放大WAF
Apple

在存储系统与数据库性能调优中,写放大系数(WAF, Write Amplification Factor) 是决定 SSD 寿命和系统写入吞吐量的核心指标。

许多工程师在测试 WAF 时,经常会遇到数据对不上的情况:为什么 RocksDB 统计的 WAF 是 4,而 SSD 盘监控到的物理写入量却翻了十几倍?

要准确测量物理写放大,必须区分两个层级的 WAF,并建立完整的观测链路:

  1. 软件/应用层 WAF(Software WAF):由数据库 LSM-Tree 整理(Compaction)或文件系统日志(Journal)引起。
  2. 硬件/物理层 WAF(Hardware/SSD WAF):由 SSD 内部闪存转换层(FTL)的垃圾回收(GC)引起。

$$\text{系统总 WAF} = \text{软件层 WAF} \times \text{文件系统 WAF} \times \text{SSD 物理 WAF}$$

本文将介绍如何使用 Fio 和 db_bench,配合底层硬件观测工具,精准测出底层的物理写放大系数


一、 核心前置条件:SSD 必须进入“稳态(Steady State)”

在新盘(Fresh Out of Box)或空盘上做任何 WAF 测试,结果都是完全错误的。
因为空盘有充足的空白闪存块,SSD 内部不需要进行垃圾回收(GC),此时物理 WAF 接近于 1。只有当 SSD 被写满至少一次以上,触发 FTL 的 GC 后,物理 WAF 才会显现真实值。

怎么做 SSD 预调理(Pre-conditioning)?

在运行正式测试前,必须对盘进行持续的随机写入,使其性能和 GC 行为达到稳定状态。

  • 对于纯 Fio 测试:使用 4KB 随机写入全盘,持续写入至少 2 倍盘容量。
  • 对于 RocksDB 测试:预先 load 超过 SSD 容量 70% 以上的数据,并让 Compaction 充分运行。

二、 使用 Fio 准确测量 SSD 物理 WAF

Fio 是块设备层面的测试工具。用 Fio 测试物理 WAF 的核心逻辑是:记录 Fio 写入的逻辑数据量(Host Writes)与 SSD 内部闪存实际写入的物理数据量(NAND Writes)的比值。

1. 采集底层的工具准备

标准的 NVMe SMART 信息中,Data Units Written 仅表示 Host 写入量。要获取 NAND(Flash)实际写入量,需要使用 SSD 厂商的专有工具。

  • Intel / Solidigm SSD
    # 安装 solidigmtool 或 isdct
    sst show -sensor
    
    寻找 NandWritesNAND Bytes Written 指标。
  • Samsung 企业级 SSD
    使用 smartctl 查看厂商自定义属性(通常是 Attribute 241/242 或特定的 NVMe Log Page):
    smartctl -a /dev/nvme0n1
    
    寻找 Physical WritesNAND Sector Writes

2. 标准测试流程

为了避免文件系统缓存和双写日志(Journal)的干扰,Fio 测试必须绕过文件系统,直接测试裸块设备,并开启 direct=1

Step 1: 记录初始值

在测试开始前,记录当前的 Host 写入总量和 NAND 写入总量:

  • $Host_{start}$ = Data Units Written $\times$ 512 KB
  • $NAND_{start}$ = 厂商工具读出的 NAND Bytes Written

Step 2: 运行 Fio 稳态随机写测试

fio --name=waf_test \
    --filename=/dev/nvme0n1 \
    --ioengine=libaio \
    --direct=1 \
    --rw=randwrite \
    --bs=4k \
    --numjobs=4 \
    --iodepth=32 \
    --size=100G \
    --runtime=7200 \
    --time_based \
    --group_reporting

注:测试时间必须足够长(建议 2 小时以上),以确保 FTL 的 GC 充分并发。

Step 3: 记录结束值

测试完成后,再次记录:

  • $Host_{end}$
  • $NAND_{end}$

Step 4: 计算物理 WAF

$$\text{SSD Physical WAF} = \frac{NAND_{end} - NAND_{start}}{Host_{end} - Host_{start}}$$

如果计算出的 WAF 明显大于 1(例如 2.5 或 3.0),说明该负载下 SSD 正在经历剧烈的 GC。


三、 使用 RocksDB db_bench 测量多层级写放大

RocksDB 作为一个 LSM-Tree 架构的存储引擎,其软件写放大非常显著。在使用 db_bench 时,我们需要理清:用户写了多少 $\rightarrow$ RocksDB 写入了多少(SST+WAL) $\rightarrow$ 操作系统/文件系统写了多少 $\rightarrow$ SSD 闪存写了多少

1. 启动 db_bench 并收集软件层 WAF

db_bench 运行结束后,会在终端或 LOG 文件中输出详尽的 Compaction Stats。

运行一个典型的随机写测试:

./db_bench \
    --benchmarks="fillrandom,stats" \
    --db=/mnt/nvme/rocksdb_test \
    --num=100000000 \
    --value_size=400 \
    --statistics=true \
    --stats_interval_seconds=10 \
    --histogram=true

在输出结果的 HeaderCompaction Stats 中,重点看这一行:

Sum(MB) write: 12345.60 (user), 54321.00 (raw), 98765.40 (actual)
------------------------------------------------------------------
Write Amp: 8.0 (actual / user)
  • User Writes: 客户端写入的 Key-Value 原始大小。
  • Actual Writes: RocksDB 实际写入到文件系统的 SST 文件和 WAL 日志的总量。
  • RocksDB WAF = Actual Writes / User Writes

2. 精准测出 RocksDB 导致的物理/硬件 WAF

仅看 RocksDB 自身的 Write Amp 是不够的,因为底层的 ext4/XFS 文件系统以及 SSD 还会二次放大。要拿到物理写放大,需要把 db_bench、/proc/diskstats 和 SSD SMART 联动起来。

闭环测试框架设计:

db_bench 运行期间,开启一个后台守护脚本,每 5 秒采集一次系统 I/O 和 NVMe 状态。

#!/bin/bash
# 监测目标盘,例如 nvme0n1
DISK="nvme0n1"

get_disk_sectors() {
    # 读出 /sys/block/nvme0n1/stat 中的第 7 个字段(写入的扇区数)
    cat /sys/block/$DISK/stat | awk '{print $7}'
}

get_nand_writes() {
    # 根据你的 SSD 厂商调整,此处以 Solidigm/Intel 为例
    sst show -sensor | grep "NAND Bytes Written" | awk '{print $4}'
}

# 记录起点
Host_Sectors_Start=$(get_disk_sectors)
Nand_Bytes_Start=$(get_nand_writes)

echo "Benchmark 开始,监控中..."
# 等待 db_bench 进程结束
wait $DB_BENCH_PID

# 记录终点
Host_Sectors_End=$(get_disk_sectors)
Nand_Bytes_End=$(get_nand_writes)

# 计算
Host_Written_Bytes=$(( (Host_Sectors_End - Host_Sectors_Start) * 512 ))
Nand_Written_Bytes=$(( Nand_Bytes_End - Nand_Bytes_Start ))

echo "Host 写入量 (OS 角度): $(( Host_Written_Bytes / 1024 / 1024 )) MB"
echo "NAND 写入量 (SSD 角度): $(( Nand_Written_Bytes / 1024 / 1024 )) MB"

通过这个闭环,你可以推导出两个极其关键的物理指标:

  1. 文件系统 + FTL 的综合物理放大率(System-to-Physical WAF)
    $$WAF_{Sys2Phys} = \frac{\text{Nand Written Bytes}}{\text{Host Written Bytes (OS 角度)}}$$
    如果这个值远大于 1.5,说明文件系统的文件碎片化严重,或者 SSD 内部发生了严重的对齐冲突。

  2. 端到端真正的物理写放大(End-to-End Physical WAF)
    $$WAF_{E2E} = \frac{\text{Nand Written Bytes}}{\text{db_bench 统计的 User Writes (Bytes)}}$$
    这个值代表了每写入 1 字节用户数据,闪存颗粒要承受多少字节的物理损耗。它是 RocksDB 参数调优(如选择 Size-tiered 还是 Levelized Compaction)的最真实反馈。


四、 避免评测偏差的避坑指南

1. 文件系统 Journal(日志)的隐形放大

在 ext4 文件系统上,默认开启了 data=ordered,这意味着元数据会被强制写入 Journal。在小数据随机写入时,Journal 的写入量可能比数据本身还要大。

  • 解决方案:测试 RocksDB 等自身有 WAL 日志的引擎时,挂载 ext4 时使用 -o noatime,nodiratime,data=writeback,或直接使用 XFS 文件系统。

2. 忽略了 Page Cache 的刷盘行为

如果 RocksDB 未开启 use_direct_io_for_flush_and_compaction,写入 SST 的流量会先进入操作系统的 Page Cache。由后台 pdflush 线程异步刷盘时,会进行 I/O 合并。
这会导致 OS 统计到的 Host 写入量显著低于 RocksDB 报告的实际写入量,呈现“写缩小”的假象,但其实只是数据还滞留在内存中。

  • 解决方案:测试结束前,必须运行 sync 强制刷盘,并多等待几分钟,直到 iostat 流量归零,再读取终点数据。

3. 未关闭 SSD 的 Trim/Discard 功能

如果挂载参数带有 discard,当 RocksDB 频繁删除旧 SST 文件时,会向 SSD 发送 Trim 指令。SSD 接收到 Trim 后会加速 GC 的进行,虽然这会释放空间,但会短时间内推高 NAND Writes。

  • 评估建议:建议在测试期间保持与生产环境一致的 Discard 配置(建议使用后台 fstrim 定时任务,而不是挂载时开启 discard),以获得最贴近真实的生产 WAF 表现。

点评评价

captcha
健康