SSD 的写入性能并非一成不变。一块全新的 SSD 顺序写入可以跑到 3 GB/s,但当盘接近写满时,性能可能骤降至几百 MB/s 甚至更低,延迟也会出现剧烈抖动。这一切的根源在于 NAND 闪存的物理特性,以及 SSD 内部的垃圾回收(Garbage Collection, GC) 机制。而 TRIM(又称 Discard / Deallocate)正是操作系统与 SSD 之间沟通”哪些数据不再需要”的关键桥梁。
本文将从 NAND 闪存的物理结构出发,逐步深入到 GC 机制、TRIM 命令的工作原理,以及在不同存储栈中的工程实践。
NAND 闪存的物理结构 (1)
NAND 闪存有两个核心概念:
| 概念 | 说明 | 典型大小 |
|---|---|---|
| Page | 最小读写单位,只能整 page 读写 | 4 KB / 8 KB / 16 KB |
| Block | 最小擦除单位,包含多个 page,只能整 block 擦除 | 256 KB ~ 数 MB(如 64 × 4 KB) |
除了必须整 page 读写(假如不对齐,则RMW: Read-Modify-Write),还有一个关键约束:
NAND 只能向已擦除(erased)的 page 写入数据,不能像 HDD 那样直接覆盖。
例外:Intel Optane(3D XPoint)支持 write-in-place,不受此限制,但该技术已停产。
SSD 的写入机制 (2)
由于不能覆盖写,SSD 内部维护了一张 FTL(Flash Translation Layer) 映射表,记录逻辑地址到物理地址的对应关系。
覆盖写流程 (2.1)
假设操作系统要覆盖逻辑 page-100(当前映射到物理 page-M):
1 | 1. SSD 找到一个已擦除的物理 page-N; |
注意:stale page 既不在使用中,也未被擦除(not in use but not erased),它占着空间却无法直接写入,只能等待 GC 处理。
SSD 垃圾回收 (3)
当 SSD 内部的空闲(erased)block 不足时,必须触发 GC 来回收空间。
GC 流程 (3.1)
假设 block P 包含 64 个 page,其中 61 个 stale,3 个 in use:
1 | 1. 从 block-P 中读出 3 个有效 page; |
GC 的代价 (3.2)
- 写放大(Write Amplification):为了回收 61 个 stale page 的空间,需要额外搬运 3 个有效 page。
- 延迟抖动:如果写入时恰好没有空闲 block,必须同步等待 GC 完成才能继续写入,导致尾延迟飙升。
- 寿命损耗:每次擦除都消耗 block 的 P/E 寿命。
核心矛盾:SSD 不知道哪些数据已经”逻辑上”不再需要,只能被动等待覆盖写时才发现 stale page。如果 SSD 能提前知道,就可以主动、从容地在空闲时做 GC,而不是在写入路径上被迫同步 GC。
问题:文件系统删除 ≠ SSD 感知 (4)
对于覆盖写,SSD 没有问题——旧映射自然失效,SSD 知道哪些 page 是 stale 的,可以GC。
但对于文件删除(delete)和截断(truncate),问题就来了:
假如删除或截断了一个文件:
| 视角 | 动作 | 状态 |
|---|---|---|
| 文件系统 | 仅更新自己的元数据 | 逻辑 page 200~300 已释放,可复用 |
| SSD | 毫不知情,无 stale 更无 GC | 逻辑 page 200~300 仍然有效 |
文件系统只在自己的元数据(inode、bitmap)中标记空间可用,并不会通知 SSD。等到文件系统真正复用 200~300 进行覆盖写时,SSD 才知道旧数据已失效。
这意味着:
- SSD 无法提前对 200~300 进行 GC;
- 当 SSD 中 erased block 耗尽时,写入必须先触发同步 GC → 性能断崖。
TRIM:告知 SSD “这些空间不再需要” (5)
术语统一 (5.1)
不同层级和协议对同一操作有不同的命名,但语义完全相同:
| 层级/协议 | 术语 | 说明 |
|---|---|---|
| SATA | TRIM | SATA 协议中的命令名 |
| NVMe | Deallocate | 通过 Dataset Management 命令实现 |
| SCSI | UNMAP | SCSI 标准术语 |
| Linux 块设备层 | Discard | BLKDISCARD ioctl |
| Linux 文件系统 | Discard | mount -o discard / fstrim |
| SPDK BlobStore | Unmap | spdk_blob_io_unmap() |
一句话总结:TRIM = Discard = Deallocate = UNMAP,都是”通知 SSD 某些 LBA 不再使用,可以回收”。
TRIM 的作用 (5.2)
操作系统通过 TRIM 命令告诉 SSD:”逻辑地址 X~Y 的数据已无效。”SSD 收到后:
- 在 FTL 中将对应 page 标记为 stale;
- 在空闲时主动进行 GC(defragmentation),提前擦除 block;
- 后续写入时大概率已有充足的 erased block,避免同步 GC。
Linux 内核中的实现 (5.3)
1 | // 块设备层下发 discard |
判断设备是否支持 discard:
1 | $ cat /sys/block/sdc/queue/discard_granularity |
TRIM 的层级透传 (6)
TRIM 必须在所有 I/O 抽象层上逐层启用,否则命令无法到达 SSD。
以 ext4 → LVM → dm-crypt → NVMe SSD 为例:
1 | ┌─────────────────────────────────┐ |
任何一层未启用,TRIM 命令就会在该层被丢弃,SSD 永远收不到。
TRIM 策略:Continuous vs Periodic (7)
Linux 文件系统提供两种 TRIM 策略:
Continuous TRIM (7.1)
Continuous TRIM 即 实时 TRIM。
1 | mount -o discard /dev/nvme0n1p1 /mnt/data |
- 行为:每次删除文件时,立即向 SSD 发送 TRIM 命令。
- 优点:空间回收最及时。
- 缺点:
- 频繁触发 SSD 内部 GC,可能影响前台 I/O 性能;
- 若中间某层未正确启用 TRIM,不会报错,静默失败;
- 误删文件后,数据立即被标记无效,无法恢复。
Periodic TRIM (7.2)
1 | systemctl enable fstrim.timer # 默认每周执行一次 |
- 行为:定期(默认每周)扫描整个文件系统,一次性 TRIM 所有空闲空间。
- 优点:
- 效率高,批量下发;
- 若中间层未启用 TRIM,
fstrim会报错,便于排查; - 一周内误删文件仍有恢复机会。
- 缺点:空间回收不及时,两次 TRIM 之间 SSD 可能积累大量 stale 数据。
如何选择 (7.3)
| 场景 | 推荐策略 |
|---|---|
| 桌面 / 普通服务器 | Periodic TRIM(fstrim.timer) |
| 分布式存储(Ceph、SPDK) | 异步实时 Discard(见下文) |
| 数据库(高写入负载) | 视 SSD OP 空间而定,通常 Periodic + 配置充足的 OP |
OP: Over-Provisioning,预留空间;即对用户不可见、仅供控制器内部使用的额外 NAND 空间。
高性能存储系统中的实践 (8)
Ceph BlueStore:异步 Discard 线程 (8.1)
Periodic TRIM 对分布式存储来说回收太慢,而 Continuous TRIM 如果同步执行又会阻塞主 I/O 路径。Ceph BlueStore 的解决方案是独立的异步 discard 线程:
1 | WAL / DB / 数据删除 |
- 主 I/O 路径只负责将待 discard 的范围加入队列,不阻塞;
- 后台线程批量下发,兼顾及时性与性能;
- 仅在设备支持 discard 时启用(通过
discard_granularity判断)。
SPDK 用户态:绕过内核 (8.2)
SPDK 使用用户态 NVMe 驱动,完全绕过 Linux 内核块设备层,因此:
- 没有
/sys/block/...; - 没有
BLKDISCARDioctl; - 直接通过 PCIe 下发 NVMe Dataset Management(Deallocate) 命令;
- BlobStore 层对应 API 为
spdk_blob_io_unmap();
1 | spdk_blob_io_unmap() [BlobStore 层,异步] |
注意:无论是内核态的 BLKDISCARD 还是用户态的 NVMe Deallocate,都只是通知 SSD 标记无效,物理擦除仍然由 SSD 固件在后台异步完成,无法强制立即擦除。
安全注意事项:加密层的 TRIM (9)
在 dm-crypt / LUKS 加密卷上启用 TRIM(--allow-discards)存在信息泄露风险:
- 攻击者可以观察哪些 block 被 discard,推断出加密卷中哪些区域有数据、哪些是空闲的;
- 结合文件系统结构知识,可能进一步推断文件分配模式。
建议:在高安全要求场景下,加密层不要启用 discard 透传。
总结 (10)
1 | ┌──────────────────────────────────────────────────────────────┐ |
核心要点:
- NAND 不能覆盖写,只能写 erased page,擦除以 block 为单位 → 必须有 GC。
- 文件系统删除不通知 SSD → SSD 无法提前 GC → 写入时可能被迫同步 GC → 性能断崖。
- TRIM 是桥梁:告知 SSD 哪些 LBA 已无效,让 GC 从容进行。
- TRIM ≠ 立即擦除:它只是通知,物理擦除由 SSD 固件异步完成。
- 工程实践:通用场景用 Periodic TRIM;高性能存储用异步 discard 线程;SPDK 用户态直接下发 NVMe Deallocate。
- 保障写入性能的关键:充足的 OP 空间 + 及时的 TRIM + 企业级 SSD 固件。