存储硬件: NAND Flash / SSD FTL / 写入放大 / NVMe
TL;DR
数据库的 WAL、LSM-Tree 的 compaction、分布式系统的 fsync——所有"持久化"的语义最终都落在 SSD 内部的 NAND flash 上。SSD 不是"快一点的磁盘":它不能覆盖写(只能先擦除后写)、有寿命上限(P/E 次数)、内部还有一层把你完全屏蔽的 FTL(闪存转换层)。这一章把 NAND 物理特性(SLC/MLC/TLC/QLC)、SSD 内部机制(磨损均衡、垃圾回收、写放大、掉电保护)、NVMe 协议(队列对、多命名空间)讲透,并回答数据库工程师最关心的:为什么 LSM 写放大要对着 SSD 写放大一起算。
读完应能:
- 说出 NAND 的"读页 / 写页 / 擦除块"三粒度,以及 SLC→QLC 的寿命与速度差异。
- 解释 FTL 的 L2P 映射、磨损均衡、垃圾回收如何共同决定"写放大系数",并估算 1 次逻辑写实际消耗多少物理写。
- 说清
TRIM、OP(预留空间)、掉电保护各自解决什么问题。 - 讲出 NVMe 与 AHCI/SCSI 的队列模型差异(多队列、polling、无锁提交/完成)。
- 把 SSD 特性翻译成数据库设计决策:WAL 为什么写少量页、LSM 为什么反而被 SSD 喜欢、QLC 时代怎么选盘。
一、HDD vs SSD: 两种完全不同的物理
| HDD | SSD | |
|---|---|---|
| 随机读 | 寻道 ~10ms | ~50-100µs(受队列深度影响) |
| 随机写 | 寻道 ~10ms | 与顺序写接近(FTL 内做批量) |
| 写单元 | 扇区 512B/4K | 页 4-16KB(最小写粒度) |
| 擦除单元 | 无 | 块 4-16MB(最小擦除粒度) |
| 覆盖写 | 直接覆写 | 必须先擦除(无覆盖写) |
| 寿命 | 近乎无限 | P/E 次数有限 |
一句话:SSD 把"随机 IO"的物理代价抹平了,但把"寿命 + 空间放大"的账转嫁给了固件和上层软件。
二、NAND flash 物理
NAND 用浮栅/电荷陷阱晶体管存电荷表示 1 位或多位。每单元存几位决定密度、速度、寿命的三角关系:
| 类型 | 每单元位数 | 电压档数 | 相对寿命 | 相对速度 | 用途 |
|---|---|---|---|---|---|
| SLC | 1 | 2 | ~10x | 快 | 企业缓存/系统盘 |
| MLC | 2 | 4 | ~4x | 中 | 高端消费/企业 |
| TLC | 3 | 8 | 1x | 中慢 | 主流消费/云 |
| QLC | 4 | 16 | ~0.3x | 慢 | 大容量冷数据 |
干扰与衰减:
- 写干扰:向一个页编程会扰动同块其他页的电荷;
- 读干扰:反复读一个页会累积扰动(读多了要先搬移数据再读);
- 数据保持:电荷随时间泄漏,TLC/QLC 在高温下保持期显著缩短——冷数据盘要定期刷新。
三、SSD 内部: FTL 是那个"看不见的数据库"
3.1 逻辑到物理映射(L2P)
主机看到的是连续逻辑块(LBA),实际写到哪里由 FTL 决定:
主机: LBA 1000 写入 4KB
FTL: 找空闲页 (可能在任何物理块), 写入, 更新 L2P 表
(L2P 表本身按 4KB 粒度记录映射, 1TB 盘 ≈ 256M 条)
- 这就是为什么 SSD 随机写和顺序写性能接近——FTL 把随机逻辑写重排成顺序物理写;
- L2P 表大,SRAM 放不下,所以有 DRAM 缓存(断电丢)或 HMB/无 DRAM 设计(用主机内存)。
3.2 磨损均衡(Wear Leveling)
同一个块不能反复擦除。FTL 记录每个块的擦除次数,尽量均匀使用:
- 动态均衡:只在写时选择最年轻块;
- 静态均衡:把长期不动的冷数据搬到快坏死的块上(让热数据用年轻块)——牺牲一点迁移 IO 换整盘寿命。
3.3 垃圾回收与写入放大
覆盖写 = 把旧页标记无效 + 写新页。无效页要等块整块回收:GC 把块里还有效的页搬走,再擦除整块:
写放大 WA = 实际写入 NAND 的物理字节 / 主机下发的逻辑字节
典型: TLC 盘顺序写 WA≈1.1-1.5, 随机小写 WA 可达 3-10+
影响 WA 的旋钮:
- OP(Over-Provisioning,预留空间):出厂多留 7%-28% 物理块不暴露给主机。OP 越大,GC 越从容、WA 越低、寿命越长;
- TRIM:主机告诉 SSD"这些 LBA 数据已删除",FTL 把对应页直接标无效,避免 GC 搬死数据;
- 写入模式:随机 4K 小写是 WA 噩梦;顺序大块写接近 1。
note
数据库与 WA 直接相关:LSM-Tree 的 compaction 把 N 次小写重写成更少的大写,把 WA 从 3-10 压向 1.x,所以 LSM 天然适合 SSD(见 databases/indexing/lsm.md)。反过来 B+ 树随机页写要靠 FTL 兜底,WA 更高。
3.4 掉电保护
写一半断电,FTL 映射表可能撕裂:
- 企业盘:带电容(power-loss protection),掉电后靠电容把 DRAM 里未落盘的映射表/数据刷进 NAND;
- 消费盘:常无保护——这正是"数据落到 page cache 不代表安全"、"必须 fsync"的硬件注脚。
四、NVMe: 为并行而生的协议
NVMe 替代 SATA/AHCI 的关键是队列模型:
旧 AHCI: 1 个队列, 深度 32, 命令要敲门铃 + 中断 → 单队列串行
NVMe: 64K 个队列对, 每个队列深 64K
Submission Queue (主机→盘) / Completion Queue (盘→主机)
doorbell 写一次, 批量取命令; 支持 polling(无中断)
- 多核 CPU 每个核自己的队列 → 无锁提交,IOPS 从几万涨到百万级;
- 中断聚合(interrupt coalescing)+ polling 模式:低延迟场景(数据库 fsync)用 polling 可把尾延迟降一半;
- 多命名空间(namespace):一块盘切成多个逻辑盘,可独立配额/加密;
- ZNS(Zoned Namespace):把"顺序写"语义暴露给主机,让数据库/LSM 直接管理块——省掉 FTL 的部分搬运,下一代存储选型点。
五、对上层软件的三条铁律
- fsync 是昂贵的:NVMe 单次刷盘 ~10-50µs 但涉及掉电保护语义 + 队列 flush,批量合并 fsync 是 WAL 高吞吐的命门;
- 顺序大块 > 随机小块:能批量就批量;
O_DIRECT大 IO +io_uring多队列是数据库/存储引擎的标准姿势; - 寿命按 TBW 算预算:TLC 盘 TBW 有限,日志类高写负载选企业 TLC/MLC + 大 OP,QLC 只放冷数据。
warning
常见误判:把消费级 NVMe 当企业盘用在高写入日志系统,1-2 年盘就报错掉盘。选盘先看 TBW(总写入字节)与负载的写入速率,而不是顺序读速度。
六、一页速查
粒度: 页(4-16KB, 写) < 块(4-16MB, 擦除); 无覆盖写
寿命: SLC>MLC>TLC>QLC; TBW 是预算, 写放大决定实际消耗
FTL: L2P 映射 + 磨损均衡 + GC + TRIM + OP + 掉电保护
写放大: 随机小写 3-10x, 顺序大块 ~1.x, OP/TRIM 压低
NVMe: 64K 队列对 + polling, 百万 IOPS, ZNS 把顺序语义交还主机
数据库: WAL 批量 fsync; LSM 对 SSD 友好; QLC 只放冷数据