Skip to content

新盘跑分很快,用了一个月就慢下来了 ​

选型时对几块固态盘各跑了十分钟的随机写测试,挑了数字最高的那款。上线一个月后,同样的测试命令,结果比当初低了一大截,而且波动很大。盘没有坏,健康指标一切正常。

固态盘的写入性能不是一个固定的数:它取决于盘当前的状态,而状态取决于此前写过多少、怎么写的。全新的盘和写满过的盘,是两种不同的状态。十分钟的测试量到的是前一种,生产环境长期处于后一种。

本文说明这个差别的来历,以及怎样测到能代表生产状态的数字。

本文没有配套实验。 稳态测试需要在一块可以被完全覆盖的真实固态盘上长时间写入,本站的实验环境不具备这个条件。下面的内容来自存储行业公开的测试规范、设备厂商文档和内核文档,不含本站实测的数字;图中的曲线是示意。

写入速度持续写入的时间全新状态过渡稳态空块充足直接写入空块用完垃圾回收开始介入每写一份用户数据盘内部要多搬运几份评估一块盘或一个存储方案,要先把盘写满一到两遍,再测到指标不再变化为止预留空间越多、写入越顺序,稳态下盘内部的搬运越少,速度越接近全新状态曲线形状是示意,不同型号下降的幅度与时间差别很大
图 1 · 示意(按公开的测试规范与原理,不是本站实测):固态盘不能原地覆盖,只能写进已经擦除的块。全新的盘到处是空块,写入很快;空块用完之后,每次写入都要先由盘内部的垃圾回收腾出空块,速度下降并出现波动;持续写入一段时间后稳定在一个较低的水平。短时间的基准测试量到的是第一段。

一、不能原地覆盖 ​

闪存有两个与机械盘完全不同的性质:

  • 写入只能写到已经擦除过的位置,不能直接覆盖旧数据。
  • 写入的最小单位是页(通常几 KB 到十几 KB),擦除的最小单位是块(由几百个页组成,几 MB)。要擦除就得整块擦。

所以「修改一个页」的实际过程是:把新内容写到别处一个空闲的页,把原来的页标记为无效。盘内部维护一张从逻辑地址到物理位置的映射表,主机看到的地址不变。

无效的页越来越多,空闲的页越来越少。要重新得到空闲的页,盘的控制器必须做垃圾回收:选一个无效页比较多的块,把里面仍然有效的页搬到别处,然后把整块擦除。

二、三个阶段 ​

全新状态。 盘是空的,到处都是擦除好的块。每次写入直接落到空闲页上,不需要任何额外的搬运。这时测到的是闪存和控制器的最高速度。

过渡。 写入的总量接近盘的容量之后,空闲的块用完了。之后每写入一点,垃圾回收就得先腾出相应的空间。前台写入和后台搬运争抢同一份资源,速度明显下降,并且出现波动。

稳态。 继续写下去,垃圾回收的工作量与写入量达到平衡,性能稳定在一个比全新状态低的水平。

稳态下,主机每写入一份数据,盘内部实际要写入不止一份(多出来的是被搬运的有效页)。这个倍数就是盘内部的写放大。它不是定值,取决于下面几个因素。

三、什么决定稳态的水平 ​

预留空间。 盘的实际闪存容量比标称容量大,多出来的部分主机看不到,专门给垃圾回收周转。预留越多,回收时每个块里的有效页越少,搬运越少。企业级的盘预留比例普遍高于消费级的盘,这是两者在持续写入下表现不同的主要原因之一。自己也可以增加预留:分区时留一部分空间不用,前提是这部分空间从未被写过或者已经被释放。

盘用了多满。 道理相同:有效数据占得越满,可供周转的空间越少。一块用到九成满的盘,持续写入的表现比用到五成满时差。

写入的模式。 顺序写入、大块写入时,一个块里的页往往一起失效,回收时几乎不用搬运。小块的随机写入让每个块里有效页与无效页混在一起,搬运最多。所以稳态下顺序写的速度与全新状态相差不大,随机写的速度下降最明显。

随机小块写:有效页与无效页混在一个块里块 A有效无效有效无效无效有效搬运 3 页块 B(空块)有效有效有效新空空块 A 整块擦除后才重新可用;主机写 1 页,盘内实际还写了 3 页顺序大块写:一个块里的页一起失效块 C无效无效无效无效无效无效直接擦除块 C(擦除后)空空空空空空不用搬运,稳态下与全新状态相差不大预留空间越多、盘用得越空、释放通知(TRIM)传到了盘,回收时要搬的有效页就越少
图 2 · 示意(按闪存原理,不是本站实测):主机改了一个页,盘内只能把新内容写到空页、把旧页标记为无效。空页用完后,垃圾回收挑一个无效页多的块,把剩下的有效页搬走、整块擦除。被搬运的页就是盘内多出来的写入。顺序大块写入时一个块里的页往往一起失效,几乎不用搬;随机小块写让有效页和无效页混在一起,搬运最多

释放通知。 文件系统里删除了文件,盘并不知道那些地址上的数据已经没用,垃圾回收时还会把它们当作有效数据搬来搬去。释放指令(SATA 里叫 TRIM,NVMe 里叫 Deallocate,SCSI 里叫 UNMAP)就是用来告诉盘「这些地址不用了」。Linux 上通常用定期执行的 fstrim 完成,也可以在挂载时打开实时的 discard。没有释放通知时,盘在自己看来始终是满的。

缓存策略。 很多消费级的盘把一部分闪存当作高速缓存来用,写入先进缓存,空闲时再整理到主存储区。缓存没用完时速度很高,用完之后直接降到主存储区的速度,下降的幅度可以非常大。这是另一种「短测试很快」的来源,与垃圾回收无关,但效果叠加。

上层软件的写入方式也会放大或减轻这个问题。LSM 树的压实把业务写入量放大了几倍,这些放大的写入同样要计入盘的负担和寿命,见写了 180 MB,磁盘实际写了 700 MB。

四、怎么测才算数 ​

存储行业的测试规范(SNIA 的固态存储性能测试规范)对此有明确的步骤,思路是三步:

  1. 清空。 用安全擦除或全盘释放把盘恢复到一个确定的初始状态,让每次测试从同一个起点开始。
  2. 预处理。 先把整块盘顺序写满,通常写两遍,让每个逻辑地址都有数据;再用待测的负载持续写入。
  3. 测到稳定为止。 分轮次记录指标,直到最近几轮的结果变化落在规定的范围内,才取这个窗口里的值作为结果。

自己做选型测试时至少要做到:

  • 写入的总量是盘容量的数倍,而不是按时间跑十分钟。
  • 看整条曲线,而不是只看平均值:记录每秒的吞吐与延迟,观察什么时候下降、降到多少、波动多大。
  • 关注延迟的高分位。稳态下平均吞吐可能还过得去,垃圾回收造成的偶发长延迟才是影响业务的部分。
  • 测试负载接近真实负载:块大小、随机与顺序的比例、读写混合比例、并发数、盘的填充程度。
  • 在与生产相同的软件栈上测。文件系统、是否启用释放通知、上层数据库的写入模式都会改变结果。

这类测试会覆盖盘上的全部数据,并且消耗盘的写入寿命。只能在专门用于测试、没有任何数据的盘上进行,不能在有数据的盘或生产环境的盘上做。

云上的块存储另有一层:性能受购买的规格限制,有的类型带突发额度,额度用完之后降到基线。短测试量到的是突发性能,长时间运行得到的是基线性能。现象与本文类似,原因在计费模型而不在闪存。

五、与 iowait 的关系 ​

盘变慢之后,系统监控上常见的表现是 CPU 的 iowait 升高。iowait 表示的是「CPU 空闲,同时有任务在等待 I/O 完成」的时间占比,它有两个容易误读的地方:

  • 它高不代表 CPU 忙。恰恰相反,这段时间 CPU 是闲着的。
  • 它低不代表 I/O 没问题。如果同时有别的任务把 CPU 用满了,等 I/O 的时间就不会被记为 iowait。

判断存储是否成为瓶颈,更直接的是看块设备自己的指标:每个请求的平均等待时间、队列长度、设备的繁忙比例(iostat -x 里的相应列),以及应用自己记录的读写延迟分位。Linux 的压力指标(/proc/pressure/io)给出的是「有任务因为等 I/O 而停顿」的时间比例,比 iowait 更接近想知道的事。

六、检查清单 ​

  • 选型数据来自预处理之后的稳态测试,能说出盘被写入了多少倍容量。
  • 看过整条时间曲线和延迟的高分位。
  • 生产环境启用了定期的释放通知,并确认它在所用的文件系统、卷管理、虚拟化层上真的传到了盘。
  • 盘不长期用到接近全满;写入密集的场景留出额外的预留空间。
  • 监控盘的写入总量与厂商给出的寿命指标,算上上层软件的写放大。
  • 用块设备的延迟与队列指标判断存储瓶颈,不只看 iowait。

参考资料

文章以 CC BY-NC-SA 4.0 授权 · 代码片段以 MIT 授权