从一到无穷大 #101:SSD 的工作原理——从 NAND 到数据库读写

假设数据库要更新一个 4 KiB 的数据页。沿着代码往下看,是修改 buffer、写 WAL,再由刷脏线程把数据交给文件系统和块设备。到了 SSD 内部,逻辑地址的覆盖会变成地址转换、向空页写入新版本,以及稍后的数据搬迁。继续往下追,写入最终还涉及一个更底层的问题:怎样改变绝缘结构中保存的电荷,再把它读成数据。

把请求交给 SSD 以后的工作展开,需要先回答下面这些问题:

  1. SSD 断电后为什么还能保存数据?电子怎样穿过绝缘层,读取时又怎样区分不同状态?

  2. 数据库覆盖一个页,NAND 能不能直接覆盖?主机的 4 KiB 请求和闪存 page 是同一个东西吗?

  3. SSD 已经没有磁头和寻道了,为什么随机写、顺序写的表现仍可能不同?随机读又是什么情况?

  4. 为什么 SSD 写得越满,持续写入越可能变慢?删除数据能不能立刻改善,TRIM 和预留空间分别起什么作用?

  5. 一块标称每秒几 GB 的 SSD,为什么一次很小的同步 WAL 写入仍需要等待?写请求返回时,数据到底持久化到哪里了?

  6. LSM 已经把大量写入组织成顺序写,Compaction 为什么仍有代价?数据库写放大和 SSD 写放大是什么关系?

本文的系统部分主要参考 OSTEP 的 Flash-based SSDs 一章与 Coding for SSDs 系列。12 动画导读可对照 Redknot-乔红的《用最好的动画为你讲解--固态硬盘(SSD)的原理》。3 下面把器件、阵列和控制器连起来讲,物理细节用厂商资料与论文交叉核对。Code Capsule 写于 2014 年,文中的尺寸、SATA 队列和固定 I/O 大小建议带有年代条件,本文保留机制,不把旧产品参数当作今天所有 SSD 的规格。讨论对象是通过常规块接口访问的 NAND SSD。

1 NAND 存储单元的电荷与读写

1.1 晶体管、储电层与阈值电压

先看一个存储单元,也就是 cell。普通 MOS 晶体管的栅极(gate)通过电场控制源极(source)和漏极(drain)之间的沟道(channel)是否容易导通。在给定源极等偏置下,让它开始导通的栅极电压需要达到一定水平,这个电压称为阈值电压。闪存利用的就是:储存电荷改变以后,cell 的阈值也会改变。4

图 1:KIOXIA NAND 教程 Figure 2 原图。控制栅下面依次是绝缘层、储电层和绝缘层,再下面是硅。图中的储电层与外部导线隔离,控制栅通过电场影响沟道。4

控制栅负责施加电场,储电层负责保存状态。经典 floating-gate(浮栅)结构把电荷保存在被绝缘层包围的导体里;charge-trap(电荷俘获)结构则利用绝缘材料中的局部陷阱保存电荷。5 在规定的使用条件下,绝缘结构能把电荷的流失压得很慢,电源撤掉以后,区分数据的阈值状态仍能保留。

所以 cell 保存的是一个物理电荷状态。0、1 或者几个 bit 的组合,是读写电路对这些状态赋予的编码。

1.2 量子隧穿与编程、擦除

绝缘层能抑制电荷流失,写入时又要改变里面的电荷,这两件事靠不同电场条件来实现。经典浮栅 NAND 利用高电场下的 Fowler--Nordheim(FN)隧穿,让电子穿过沟道与储电层之间的薄绝缘势垒。6

量子隧穿可以粗略理解为:电子具有波动性,对一个有限厚度的势垒,在另一侧仍有非零的出现概率。施加很强的电场后,势垒形状改变,隧穿电流显著增加,就能在器件可控的时间内转移电荷。这里利用的是量子传输,不应理解为先把绝缘层击穿、再把它修好。介质击穿会破坏器件。

图 2:KIOXIA NAND 教程 Figure 3 原图。左侧通过控制栅高电压把电子送进储电层,右侧通过另一组偏置移出电荷。箭头说明电荷转移方向;图里的高电压标记是示意,不是设备工作电压规格。4

以这类平面模型说明,编程(program)向储电层注入电子,提高阈值;擦除(erase)使它回到较低阈值状态。撤掉编程、擦除偏置以后,隧穿电流大幅降低,电荷得以保留。断电保存也有条件:绝缘层的缺陷、温度和已经经历的擦写会影响 retention(数据保持能力),不能据此推出永远不丢数据。6

下面会介绍 3D NAND。其储电材料、沟道与擦除偏置有不同实现,图中的平面剖面只是解释电荷转移的起点;不能用它规定所有现代器件的具体擦除路径。

1.3 读取感测与多 bit 编码

读取时,电路在控制栅上施加参考电压,再由感测电路判断沟道是否导通。以图里的 SLC 编码为例,电子较多、阈值较高的状态在这个参考电压下不导通,对应 0;阈值较低的状态导通,对应 1。4 读取的目标是感测储存电荷造成的电气差异,通常不需要把那些电荷取出来。

图 3:KIOXIA NAND 教程 Figure 4 原图。两侧施加相同的控制栅读取电压,储存电荷不同,导通结果也不同。这是图中 SLC 的 0/1 对应关系,多 bit cell 还需要更多阈值区间。4

一个状态在许多 cell 上测出来,会形成阈值分布,并不会所有 cell 都恰好落在同一个电压点。读取参考电压要放在这些分布之间,才能留出判断余量。

图 4:KIOXIA 多电平教程 Figure 2 原图。横轴是 cell 阈值电压,纵向表示 cell 数量;中间虚线是读取参考电压。两个分布分别对应图中的 1 和 0。7

SLC 每个 cell 编码 1 bit,需要区分两个状态;通常所说的 MLC 编码 2 bit,需要四个状态;TLC 是 3 bit、八个状态;QLC 是 4 bit、十六个状态。多 bit 编码要求把阈值控制在更细的区间,并根据若干参考电压的感测结果恢复相应 bit。7

图 5:KIOXIA 多电平教程 Figure 5 原图。增加每个 cell 的 bit 数,需要分辨更多电压状态。图中位串用于说明编码,不能当作所有厂商通用的状态映射;也不能由 TLC 有八种状态,推出每次读取必定执行七次比较。7

写入也要控制精度。ISPP(Incremental Step-Pulse Programming)把编程拆成一轮轮短脉冲和校验:施加脉冲,检查阈值是否达到目标,再决定哪些 cell 继续编程、哪些停止。这样才能让一组 cell 落进较窄的目标分布。8 具体器件还可能分多个编程阶段完成,不能把一次主机 write 理解成给 cell 加一次电压就结束。

同等条件下,每 cell 编码更多 bit 能提高容量、降低单位容量成本,同时需要承担更精细的编程、读取与纠错成本。这个关系能帮助理解不同 NAND 的取舍,但整盘表现还要加上控制器、并行资源、缓存和固件。

2 NAND 阵列、page 与 block

2.1 String、wordline 与 bitline

大量 cell 需要组织成能选中、感测和写入的阵列。NAND 把多颗存储晶体管串联成一条 string,string 通过选择晶体管连接 bitline;wordline 则连接一行 cell 的控制栅。bitline 是通向感测等外围电路的导线,string 是串联晶体管链。9

图 6:Cai 等 2017 年论文 Figure 8 原图,裁自 arXiv 版本 PDF 第 8 页。竖向是带选择管的 NAND string,横向 wordline 连接对应行 cell,bitline 连接感测路径。图中的 N、M 及省略号表示阵列规模的简化示意,不代表当前器件的具体行列数。9

读取某个 cell 时,同一串联通路上的其他 cell 也必须允许电流通过,所以未选中的 wordline 会施加 pass voltage,选中的 wordline 使用读取参考电压,再由 bitline 一侧的电路感测。9 这也解释了为何 NAND 的读取涉及一条串联通路及其偏置,不能只看一个孤立晶体管。

page 是面向阵列读写的数据组织单位。对于多 bit cell,同一组 cell 的不同 bit 位置可以组成不同的逻辑 page,例如 MLC 的 lower/upper page;因此 cell 到 page 的关系不能简单理解成每个 cell 只属于某一个互不重叠的 page。9

2.2 Page 编程与 block 擦除的粒度

一个 erase block 包含许多 page。NAND 按 page 组织读取和编程,按更大的 block 擦除,已经编程的位置不能像 DRAM 那样任意覆盖。1 编程时可以通过 wordline、bitline 偏置选择和抑制相应 cell;擦除则按照阵列共享的偏置与外围电路组织,对一组 cell 统一操作。隧穿解释电荷怎样改变,阵列组织解释为什么操作有这样的粒度,两者需要连起来看。

这里用一个非常小的教学模型:每个 block 只有四个 page,已经放了 A、B、C、D。如果坚持把 A' 写回 A 的原位置,就要先保住另外三页的数据,擦除整块,再把需要的内容写回来。为了改一页,把其他数据也搬一遍,这个代价显然很难接受。后面的 FTL 会用异地更新来处理这个问题。

真实器件还有编程顺序和阶段等约束。数据库 page、文件系统 block、操作系统内存页、主机逻辑块、NAND page 和擦除 block 属于不同层次;大小碰巧相同,也不会自动变成同一个对象。

2.3 3D NAND 的垂直阵列与可靠性

3D NAND 把存储单元沿垂直方向组织,提高单位平面面积内的 cell 数量。以 KIOXIA BiCS 的示意为例,沟道沿柱状方向贯穿多层控制栅,每层控制栅与沟道、储电结构共同形成一个 cell;许多柱状 string 再组成阵列。10

图 7:KIOXIA BiCS 教程 Figure 5 原图。右侧把平面示意旋转后对应到柱状结构,左侧展示围绕垂直沟道的控制栅、绝缘层和储电层。这是结构示意,实际制造并非把一张平面芯片机械卷起来。10

图 8:KIOXIA BiCS 教程 Figure 3 原图,展示垂直 string 与多层阵列的组织关系。图中层数仅用于说明结构。10

3D 描述空间组织,TLC、QLC 描述阈值编码,floating gate、charge trap 描述电荷存储结构,它们是不同维度。Solidigm 在 2023 年的公开资料里就介绍过采用 vertical floating-gate 的 3D QLC。11

前面的物理过程也带来了可靠性约束。P/E(编程/擦除)循环的高电场会累积氧化层与界面缺陷;电荷随时间变化会影响 retention;读取和编程施加的电压还可能扰动邻近或未选中的单元。6 read-retry 可以调整读取条件重新感测,ECC(纠错码)可以修正一定范围的错误,控制器还会管理坏块和搬迁数据,但这些能力都有范围,无法取消介质磨损。9

3 FTL 的地址映射与异地更新

SSD 对主机提供一段逻辑地址空间,主机用 LBA(Logical Block Address)指定读写位置。控制器里的 FTL(Flash Translation Layer)负责把这些逻辑地址转换成实际的 NAND 位置,并安排后续的读、编程和擦除。1

图 9:OSTEP Figure 44.3 原图,裁自 PDF 第 7 页。接口逻辑接收主机命令,控制器管理闪存,内部内存可用于缓存、缓冲和映射等元数据。这是逻辑示意,图里的 Flash 方块数量和连接方式不代表具体产品的通道配置。1

继续前面的例子。为了把地址转换单独讲清楚,下面假设每份数据恰好占一个 FTL 映射页,并用 L0 表示逻辑页、P0 表示物理页;它们不是在规定真实 SSD 的 LBA 或 page 大小。

text 复制代码
初始:
映射    L0 → P0     L1 → P1     L2 → P2     L3 → P3
block X [ P0:A   | P1:B   | P2:C   | P3:D   ]
block Y [ P4:空  | P5:空  | P6:空  | P7:空  ]  ← 已擦除

更新 L0、L1 后:
映射    L0 → P4     L1 → P5     L2 → P2     L3 → P3
block X [ P0:A旧 | P1:B旧 | P2:C   | P3:D   ]
block Y [ P4:A'  | P5:B'  | P6:空  | P7:空  ]

新版本写入 P4、P5,映射随之改变;后续读取 L0,就去找 P4。这条读取路径可以先理解为:查询映射、定位 NAND、读取并纠错、把数据返回主机;命中设备缓存时,部分步骤可以省去。P0、P1 里的电荷状态仍然存在,但已经没有当前逻辑页引用它们,成为失效页。失效和已擦除是两回事,失效页暂时还不能拿来写新数据。12

这就是异地更新(out-of-place update)。主机眼里的覆盖,在设备内部表现为向空闲位置追加新版本。擦除成本被推迟了,接下来还得有人处理旧版本。

一笔写请求交给控制器后,会涉及数据缓冲、纠错编码、物理页分配与编程,以及映射更新。哪些工作必须在返回前完成,还取决于后面的缓存和同步语义。9

映射本身也要付出成本。假设一块 1 TiB 的设备按 4 KiB 粒度映射,每条映射只用 4 字节,仅这张理想化的表就需要 1 GiB 内存,还没算其他元数据。实际设计可以采用页级映射、块级映射、混合映射,或者把部分映射留在 NAND,只缓存活跃部分。粒度越细,更新越灵活,映射管理的开销通常也越大。112

映射还必须能在断电后恢复。仅把数据写进 NAND,却丢了哪个逻辑页指向它,依然无法正确读取。检查点、映射更新日志、页中附带的地址信息等,都是构建恢复机制可以使用的手段;具体协议取决于控制器,不能凭一个通用 FTL 示意图推断某块 SSD 的实现。1

4 GC、写放大与空闲空间

4.1 GC 的有效页搬迁与写放大

图 10:Code Capsule Part 3 Figure 4 原图,依次展示原始数据、把 x 更新为 x′、迁移有效页并擦除旧块。第二阶段表里的旧 x 已经失效,只有 x′ 是当前版本。它只用少量 page 演示逻辑关系,不规定真实器件的编程顺序。图中的一次更新与下文 A、B 两次更新是独立算例,写放大的计算以下文口径为准。12

当已擦除的空页越来越少,控制器就要执行垃圾回收,通常简称 GC:选择一个 block,把其中仍有效的数据搬走,更新映射,然后擦除原来的整块。12

回收前面的 block X,只需要保留 C、D。假设它们被迁到 P6、P7,更新对应映射以后,就可以擦除 block X。结果是四份最新数据集中到了 block Y,block X 的四页重新可写。GC 开始前还有两页空闲,搬迁占用两页、擦除释放四页,所以这次 GC 净回收两页;空闲页数恢复到两次主机更新之前的水平。

这里直接算一次设备写放大:计量区间只包含 A、B 的两页更新以及随后的这次 GC,主机写了两页,NAND 编程了四页,所以设备写放大是两倍。初始写入的四页不在这个区间内;擦除也没有作为写入字节放进分子,但它显然仍有时间和寿命成本。这个数字只是教学算例,不是某块盘的实测。

写放大通常按 NAND 实际编程字节数除以主机写入字节数来定义。12 GC 会增加分子,磨损均衡和设备元数据等内部工作也可能增加分子。因此主机每秒写入多少数据,只描述了设备工作的一部分。

如果一个候选块几乎全是失效页,回收就便宜;如果里面大部分数据仍然有效,为了释放很少的空间,就得搬很多数据。这也解释了冷热数据混在同一个 block 里的问题:热数据很快失效,长期不变的冷数据却要跟着回收一遍遍搬走。

磨损均衡还有另一层取舍。若只使用不断腾出来的热点块,某些物理块会先耗尽擦写寿命;控制器可能主动迁移冷数据,让更多块参与轮换。这是在付出额外搬迁,换取整盘寿命。12

4.2 OP 的周转空间与 TRIM 的失效通知

OP(Over-Provisioning)是设备留给内部管理、没有直接暴露成用户逻辑容量的空间。它能给异地更新、GC 和磨损均衡留下周转余地:控制器有地方先接收新数据,也更容易等待合适的块再回收。13

因而,在其他条件相近时,有效数据占用越高、可周转空间越少,持续覆盖写入就越容易承担较大的回收成本。不过,文件系统显示的使用率,还不能直接告诉我们 FTL 眼里的有效数据有多少。

数据库删除一行,可能只是写入删除标记;文件仍然占着原来的空间。即便文件系统删除了文件,如果没有把释放的逻辑范围告诉设备,SSD 仍可能在 GC 时保留那里的旧内容。各层知道的数据有效性并不一样。

TRIM/discard 把某些逻辑范围已经无须保留这件事传给设备,控制器随后回收时就可以少搬这些数据。Linux 的 fstrim 是对已挂载文件系统的空闲范围执行 discard 的一种批量方式;命令能否传到底层,还要看文件系统和中间存储层的支持。14

TRIM 不承诺立刻擦除 NAND,更不是安全删除的承诺。它提供的是失效信息,实际回收时间由设备管理。15 同样,仅缩小一个已经写满的分区,也不会自动通知 FTL 原来的内容已经无用。厂商 OP、从未写过的逻辑范围、已 discard 的范围,以及文件系统自己认为空闲的范围,需要分开理解。

这也是测试新盘和测试长期运行设备的差别。新盘有大量可直接编程的空间,短测试可能还没承担持续回收的成本。Code Capsule 专门讨论了预处理:要让测试负载把设备带入可比较的运行状态,再看持续性能。16 所以盘满以后会慢,背后有具体机制;至于留多少空间,不能从这句话直接推出一个所有产品都适用的百分比。

5 内部并行度、缓存与持久化

5.1 NAND 并行资源与主机请求并发

图 11:Cai 等 2017 年论文 Figure 1 原图,裁自 PDF 第 2 页。左侧是控制器与多颗 NAND,右侧展开主机接口、内部控制和 NAND channel。组件数量与连接是论文示意,其中 Compression、DRAM 等属于示例组织,不能视为所有 SSD 的必备配置。9

一块 SSD 内部有多条 NAND channel,通道上连接不同芯片或 die(裸片),die 内还有 plane(存储阵列分区)。不同资源可以并行或交错执行操作,也会共享总线、缓冲和控制器能力;plane 级命令另有器件约束。15 主机看到一个块设备,内部已经是一组需要调度的存储资源。

多个独立请求,或者一个足够大、可以拆分的请求,都能给设备提供利用内部并行资源的机会。队列深度(QD)描述某个观测位置尚未完成的请求数;一个请求完成后再发下一个,与同时存在许多独立请求,能利用的并行性可能差很多。在本地 PCIe NVMe 的常见实现中,主机用提交队列和完成队列与控制器交换命令及结果。这些队列位于主机内存,是环形结构,与 NAND channel 属于不同层次。17

举一个纯算术例子:假设单请求耗时为 100 微秒,程序始终只允许一个 4 KiB 请求在途,那么每秒至多完成约一万个请求,对应吞吐约 39 MiB/s。这个假设没有测量任何设备,只是在说明:单请求延迟和多请求并行时的整盘带宽,是不同的指标。NVMe 队列再深,也不会把一个有前后依赖的同步操作自动拆成许多独立工作。

增加并发到一定程度以后,更多请求主要会增加等待。GC 的读取、搬迁和擦除也要争用内部资源,因此混合读写时,读请求可能受到写入及回收干扰。SSD 的数据放置、并行访问和回收之间需要共同取舍,早期的设计研究已经把它们放在一起讨论。18

5.2 写缓存、同步语义与掉电保护

SSD 内部内存可能存映射,也可能缓冲用户数据;具体配置因产品而异。若设备开启易失性写回缓存,普通设备写请求完成时,数据可能还在易失内存中。再往上,普通 buffered write() 返回时,数据甚至可能还只在操作系统 page cache。1920

对数据库,提交路径需要约定的是掉电后仍能恢复到什么状态。Linux 通过文件系统和块层把 fsync、fdatasync 等同步要求落实到设备;涉及易失写缓存时,需要使用 flush、FUA(Force Unit Access)等机制。flush 保证相应范围内先前已经完成的写请求进入非易失存储,FUA 则要求当前写请求在满足持久化要求后才完成。它们的约束范围不同。19

PLP(Power-Loss Protection)通过保持电力和固件恢复等机制保护数据与映射元数据;带电容的企业盘可以在外部电源中断后,利用短暂供电把受保护的缓存内容写入 NAND。保护范围要看具体产品承诺。21 已发送到设备的持久化请求、仍留在主机内存的数据,以及数据库的事务提交语义,也需要分别判断。

还有一种常见缓存是 NAND 上的 SLC 缓冲区。部分产品先以 SLC 方式接收写入,再把数据迁入容量更高的 TLC/QLC 区域,以改善突发写入性能。22 它本身属于闪存,与易失 DRAM 缓存是不同对象;持续写满缓冲区以后,设备表现可能改变。因此一次小 WAL 同步的延迟、缓存内的突发吞吐、长期持续写入带宽,应该分别看。

6 实测 SSD 的接口、粒度与队列配置

设备为 Solidigm D7-P5520(SSDPF2KX076T1),标称 7.68 TB,固件 9CV10492,采用 144 层 TLC、每个 cell 存 3 bit。23 Linux 为 5.4.241-1-tlinux4-0023.1。

6.1 PCIe 链路与理论带宽

PCIe Lane 连接主机与 SSD 控制器。本盘最大能力与当前协商均为 PCIe 4.0 x4,每条 Lane 为 16 GT/s。23 PCIe 4.0 采用 128b/130b 编码,其单向链路带宽上限可按下式计算:24

B P C I e = R ⋅ L ⋅ η 8 = 16 × 10 9 × 4 × ( 128 / 130 ) 8    B / s ≈ 7.88    G B / s ≈ 7.34    G i B / s . \begin{aligned} B_{\mathrm{PCIe}} &= \frac{R \cdot L \cdot \eta}{8} \\ &= \frac{16 \times 10^9 \times 4 \times (128/130)}{8}\;\mathrm{B/s} \\ &\approx 7.88\;\mathrm{GB/s} \approx 7.34\;\mathrm{GiB/s}. \end{aligned} BPCIe=8R⋅L⋅η=816×109×4×(128/130)B/s≈7.88GB/s≈7.34GiB/s.

这里 B P C I e B_{\mathrm{PCIe}} BPCIe 是编码后的单向带宽, R R R 是单 Lane 原始比特率;本代采用 NRZ(不归零)信号,每次传输携带 1 bit,因此 16 GT/s 对应 16 × 10 9 16\times10^9 16×109 bit/s。 L = 4 L=4 L=4 是 Lane 数, η = 128 / 130 \eta=128/130 η=128/130 是编码效率,除以 8 将 bit 换成 byte;GB 按十进制、GiB 按二进制换算。

PCIe 支持全双工,主机→SSD 和 SSD→主机两个方向各有约 7.88 GB/s 的编码后链路上限,分别承载主要的写数据和读数据;这个数尚未扣除数据包和链路维护开销。

设备顺序读写性能需要另外看。厂商给出的 D7-P5520 系列 128K 顺序读/写标称最高为 7,100/4,200 MB/s,即 7.1/4.2 GB/s。23 这些是系列规格值,本次配置读取没有实测读写峰值;混合读写还会竞争控制器、NAND 等资源,全双工也不保证两个设备峰值能同时达到。

6.2 NVMe 队列

本盘使用 NVMe 1.4,SQ 为提交队列,CQ 为完成队列。队列配置如下:

检查对象 本次结果 读取依据
已分配 I/O SQ 数量 135 Number of Queues 当前值
已分配 I/O CQ 数量 135 Number of Queues 当前值
Linux I/O 队列数 135 queue_count=136,含一个管理队列
本次单 I/O 队列容量 1024 entries sqsize=1023
控制器单 I/O 队列容量上限 4096 entries CAP.MQES=4095
每个块层硬件队列的请求标签数 1023 nr_tags

Number of Queues 当前值 0x00860086 中,SQ、CQ 数量均采用零基编码,各表示 135 个;sqsize 同样保存容量减一。2526 这些队列共享控制器与介质,允许多个独立写请求在途;队列容量、当前在途数和 NAND 内部并行度需要分别看。并发覆盖同一 LBA 的顺序仍由应用协调。

6.3 主机写入粒度与原子性

两个 namespace(独立逻辑地址空间)各为 3,827,889,602,560 B,均采用 4 KiB LBA,附加 metadata 为 0。

检查对象 本次结果 读取依据
NVMe Write 最少寻址数据量 4 KiB 1 个完整 LBA
单命令最大传输量 128 KiB MDTS=5、CAP.MPSMIN=0
正常运行时原子写单位 4 KiB AWUN=0
掉电或错误时原子写单位 4 KiB AWUPF=0
推荐写入粒度 4 KiB NPWG=0
最优写入大小 4 KiB NOWS=0
Linux discard 粒度 4 KiB discard_granularity

NSFEAT 表明本盘采用控制器的原子写参数,推荐粒度字段有效;AWUN、AWUPF、NPWG、NOWS 的零值均表示一个 LBA。25 4 KiB 是设备命令的最小寻址量,buffered write() 仍可只写一个字节。较大请求可以合法提交,但超过 128 KiB 需要拆分,超过 4 KiB 也不能仅凭 AWUN/AWUPF 承诺整条请求的原子性。

6.4 NAND 几何与 GC 参数

NAND Channel 连接控制器与闪存,数量不能由 PCIe x4 或 NVMe 队列数推算。本次 Identify 与已核厂商资料未披露 Channel 数、NAND Page 字节数、每个擦除 Block 的 Page 数和 Block 大小。TLC 的 3 bit/cell 与 144 层也不足以换算这些值。

介质按 NAND erase block 擦除,4 KiB LBA、physical_block_size 和 discard 粒度均不能替代它。927 设备支持 Dataset Management/deallocate,主机可通知逻辑范围失效,实际擦除由固件安排。GC 触发水位、victim 选择、搬迁批次、前后台调度及并行任务数本次均未核实。

7 数据库访问模式对应的设备代价

现在可以回到随机与顺序的区别。SSD 消除了机械寻道,但请求大小、访问局部性、映射查找、内部放置和回收效率仍会影响性能。随机写和顺序写描述的是主机逻辑地址;经过 FTL 以后,它们都可能被写进新的物理位置,逻辑连续也不保证物理连续。28

对小随机覆盖,若更新散布在很大的地址范围,许多 block 可能都只失效了一小部分,GC 就需要搬较多有效数据。若冷热分布、映射和放置策略更有利,随机写的代价又可能低很多。顺序写通常更容易合并请求、利用传输和编程粒度,但收益有多大,仍取决于设备和负载。28 不能仅凭没有寻道,就把两种写入的成本划成相同。

随机读通常不产生写入那样的旧版本回收,但仍要完成映射、NAND 读取、数据传输和纠错。对于范围扫描,大请求还可以摊薄命令及软件开销;对于点查,只读取真正需要的数据又可能更划算。把所有读取都凑成大块,会增加无用读取。是否合并、预读到多大,得回到查询访问范围和并发度判断。

LSM 的 WAL、MemTable flush 和 Compaction 可以把许多更新组织成批量、顺序的文件写入,但数据会在不同阶段被反复写出。RocksDB 的调优文档用写入存储的字节数与写入数据库的逻辑字节数之比描述这类写放大,并讨论 Compaction 对它的影响。29 到 SSD 内部,这批主机写入又可能触发一次 GC 搬迁。

用一组构造数字区分这两层:假设应用提交 1 GiB 逻辑数据,WAL、flush、Compaction 和文件系统等最终向同一块设备写了 8 GiB,设备内部又编程了 12 GiB NAND。那么从逻辑数据到主机设备写入是八倍,设备写放大是一点五倍,合在一起是十二倍。这里把主机侧全部写入都计入了,不能未经拆分就把八倍全叫作 Compaction 写放大。计量窗口也应覆盖相应后台工作,避免只统计接收数据、还没开始整理的那一段时间。

顺序化可以改善设备接收工作的方式,减少重写则直接减少要交给设备的工作量,两者可以同时做。即便 Compaction 全部顺序输出,它仍会消耗读写带宽,并与 WAL 和查询争用资源。只有顺序写这个条件,还不足以判断数据库的持续写入能力。

8 结束语

一次数据库 I/O 可以继续往下展开:逻辑页如何找到物理位置,数据为什么要搬迁,写入怎样落到存储单元的电荷变化。讨论某种写法是否对 SSD 友好,至少得说清它减少了多少重写、怎样改变失效数据的分布,以及要求设备等待到哪一步。

《从一到无穷大 #102:Linux IO 路径------从系统调用到 NVMe 驱动》从读写系统调用进入 Linux,继续拆文件系统、块层和驱动,并用 eBPF 核对各层能够观察到的对象。

9 参考资料

1 Remzi H. Arpaci-Dusseau, Andrea C. Arpaci-Dusseau:Operating Systems: Three Easy Pieces --- Flash-based SSDs,Chapter 44

2 Emmanuel Goossaert:Coding for SSDs -- Part 1: Introduction and Table of Contents,2014

3 Redknot-乔红:用最好的动画为你讲解--固态硬盘(SSD)的原理,2025

4 KIOXIA:What is NAND Flash Memory?

5 SK hynix:SK hynix Develops World's Highest 238-Layer 4D NAND Flash,2022,CTF 术语说明

6 KIOXIA:Reliability Handbook,2025 年 2 月,§2-1-7,第 62---63 页

7 KIOXIA:What is Multi-level Cell Technology Realizing Larger Capacity Flash Memory?

8 Yu Cai et al.:Threshold Voltage Distribution in MLC NAND Flash Memory: Characterization, Analysis, and Modeling,DATE 2013

9 Yu Cai et al.:Error Characterization, Mitigation, and Recovery in Flash-Memory-Based Solid-State Drives,2017,arXiv v3

10 KIOXIA:What is the 3D Flash Memory "BiCS FLASH™"?

11 Solidigm:QLC NAND Ready for Mainstream Use in Data Center,2023

12 Emmanuel Goossaert:Coding for SSDs -- Part 3: Pages, Blocks, and the Flash Translation Layer,2014

13 Kingston:SSD Over-provisioning (OP)

14 util-linux:fstrim(8) --- discard unused blocks on a mounted filesystem

15 Emmanuel Goossaert:Coding for SSDs -- Part 4: Advanced Functionalities and Internal Parallelism,2014

16 Emmanuel Goossaert:Coding for SSDs -- Part 2: Architecture of an SSD and Benchmarking,2014

17 SPDK:Submitting I/O to an NVMe Device

18 Nitin Agrawal et al.:Design Tradeoffs for SSD Performance,USENIX ATC 2008

19 The Linux Kernel documentation:Explicit volatile write back cache control

20 Linux man-pages:fsync(2), fdatasync(2) --- synchronize a file's in-core state with storage device

21 Kingston:SSD Power Loss Protection: How Hardware & Firmware PLP Prevent Data Loss

22 Samsung Semiconductor:Internal SSD Product Information --- What is Turbo Write?

23 Solidigm:D7-P5520 / D7-P5620 Product Brief,第 4 页介质与接口规格

24 PCI-SIG:The Journey to CopprLink™ and Beyond: An Exploration of PCIe Cabling Solutions,第 4 页 Fundamentals of PCIe Links

25 NVM Express, Inc.:NVM Express Base Specification 1.4b,Identify、Number of Queues、Write 与 Atomicity

26 Linux v5.4:NVMe PCI 驱动,队列深度与 blk-mq tag 配置

27 Linux v5.4:Queue sysfs files,设备块属性与 discard 粒度

28 Emmanuel Goossaert:Coding for SSDs -- Part 5: Access Patterns and System Optimizations,2014

29 RocksDB:RocksDB Tuning Guide --- Amplification factors

相关推荐
吹什么轩1 小时前
MySQL 内置函数全景:日期 / 字符串 / 数学 / 其他,一次讲透(附索引杀手排查)
java·数据库·面试
CS创新实验室1 小时前
改了模型,数据库为什么能自动跟上?——Django 迁移(migrations)的底层
数据库·django·sqlite
编码追梦人1 小时前
基于 ESP32-S3 的便携式多参数环境监护仪设计:从硬件选型到 FreeRTOS 多任务落地
数据库·单片机·嵌入式硬件·mongodb·esp32·freertos·低功耗设计
sunburn-1 小时前
数据库增删改查:从 SQL 基础到实践要点
java·数据库·mysql
誰能久伴不乏4 小时前
桌面客户端的运行日志系统:SQLite 分块 + 独立写线程 + 文本降级
数据库·c++·qt·架构·sqlite
程序员清风11 小时前
数据处理脚本工程化:日志、配置、命令行与测试
服务器·开发语言·数据库
正在走向自律12 小时前
数据集成产品的技术底座:从日志解析原理到 KFS 同步链路搭建(含建表与增删改查完整代码)
数据库·oracle·信创·数据集成·国产数据库·kingbasees·全量+增量同步
程序员塔塔喵12 小时前
【MySQL篇|第七篇】MySQL事务
数据库·mysql·mysql事务
独隅13 小时前
Claude API 实战教程:从入门到完整项目
android·java·数据库·claude·claude code