想象一座深夜还在营业的老图书馆。左边,一台老式唱片机慢悠悠地转着——这就是机械硬盘(HDD):盘片像黑胶唱片,磁头像唱针,从头到尾放歌很快,但你要从第三首跳到第九首,唱针得先横着抖过去,再等唱片转到目标位置,急死人。右边,一面被划成无数小格子的可擦写白板——这就是固态硬盘(SSD):往空格子里写东西飞快,可一旦白板写满了,必须像撕纸一样整块擦掉才能重用;而且同一张纸擦了写、写了擦,次数多了就再也擦不干净。图书馆管理员(FTL)手里捧着一本厚厚的地址簿,记录所有内容都堆在白板的哪个角落;你每次说“这块不要了”,管理员就悄悄在簿子上划掉,再趁深夜把整片区域擦干净腾出空位——这个提前通知的动作,就是 TRIM。这篇文章,就用这座图书馆里发生的故事,讲清楚两种存储的脾气、寿命与选型智慧。

岗位关联

理解磁盘与 SSD 的底层机制,是排查 AI 训练数据加载瓶颈、设计存储缓存策略、评估模型服务 I/O 开销的必备基本功。

目录
  1. 可视化演示
  2. 机械硬盘原理
  3. 闪存页块结构
  4. FTL地址映射
  5. TRIM与垃圾回收
  6. 磨损均衡与OP
  7. 性能指标对比
  8. 存储选型策略
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

对比HDD机械硬盘与SSD固态硬盘的存储结构,展示TRIM命令与垃圾回收GC机制如何优化SSD写入性能。

本节配有交互动效(进入视口后自动加载,约 3KB)。

存储结构对比 HDD: 磁头寻道读写盘片磁道 SSD: 电信号擦写NAND闪存页块 WA = 写入放大 = 闪存实际写入 / 主机写入 HDD随机IOPS约100-200,SSD随机IOPS可达10万+

机械硬盘原理

深夜图书馆的左侧,那台老唱片机又开始工作了。黑胶唱片(盘片)在电机带动下匀速旋转,唱针(磁头)悬在唱臂末端。你想听第三首?唱臂得先横着摆过去,把唱针落在第三首对应的那一圈音轨上,这一抖,就是寻道;落点还不一定恰好是那段音轨的起点,还得等唱片悠悠转过来,这一等,就是旋转延迟。机械的动作,是这台机器心里永远的痛。

硬盘(HDD)的核心就是“旋转的盘子 + 会移动的针”。盘片表面一圈圈同心圆叫磁道(track),每圈又被切成一个个扇区(sector),数据就躺在扇区里。访问一个扇区,物理上必然经历三段:磁头横移(寻道)、等待目标扇区转到磁头下方(旋转延迟)、然后连续读取(传输)。

三段耗时里,寻道和旋转延迟是纯机械开销,也是随机访问慢的根源。寻道时磁头臂像唱臂一样咻地一甩,最快也要几毫秒;而旋转延迟平均要等盘片转半圈:

所以唱片机只适合“从头放到尾”的顺序听歌,最怕一会儿切一首、一会儿往回跳——每切一次,唱针都要重新抖一下、唱片都要重新转半圈。机械硬盘在随机小 IO 上的表现,正是这种“抖一下、等半圈”的宿命。

Taccess=Tseek+Trotational+Ttransfer\displaystyle T_{access} = T_{seek} + T_{rotational} + T_{transfer}
一次硬盘访问的总时间 = 寻道时间 + 旋转延迟 + 传输时间。旋转延迟约等于转半圈所需时间,转速越快、数值越小。
  1. 访问总时间 $T_{access}$一次完整读写从发出到完成的耗时,是三项时间之和。
    Taccess=Tseek+Trotational+Ttransfer\displaystyle T_{access}=T_{seek}+T_{rotational}+T_{transfer}
  2. 寻道时间 $T_{seek}$磁头在磁道间机械移动的时间,平均约 5–10ms,是随机访问中波动最大的部分。
    Tseek≈5∼10 ms\displaystyle T_{seek} \approx 5 \sim 10\ \mathrm{ms}
  3. 旋转延迟 $T_{rotational}$目标扇区转到磁头下方的平均等待时间,相当于盘片转半圈,转速为 7200RPM 时约 4.17ms。
    Trotational=12×60RPM\displaystyle T_{rotational} = \frac{1}{2} \times \frac{60}{RPM}
  4. 传输时间 $T_{transfer}$数据从盘面连续读入内存的时间,相比前两项极小,常被忽略。
    Ttransfer≈数据量持续传输速率\displaystyle T_{transfer} \approx \frac{\text{数据量}}{\text{持续传输速率}}

闪存页块结构

白板区完全是另一种脾气。一大面白板被划成许多“页”,每 4 页组成一个“块”,管理员手里有一支特殊的笔:写一个新格子很快,但想改一个旧格子?不行。你得先拿起橡皮,把整个块(4 页)全部擦干净,再重新写。而且这块白板纸有擦写次数上限,擦多了表面就花了。

NAND 闪存的物理组织正是如此:最小读写单位是页(page,通常 4–16KB),一个块(block)包含几百页;页可以被单独写入和读取,但擦除只能以块为单位。写入一个已有内容的页之前,必须先擦除它所在的块——所以主控不敢随便擦,只能把数据写到空白页上,旧数据先留着,等以后一块儿收拾。

更微妙的是寿命。闪存单元靠“困住电荷”表示 0 和 1,每次擦除都会磨损绝缘层。SLC/MLC/TLC 每单元分别存 1/2/3 比特,存储密度越高,电荷越难分辨,可擦写次数越少。SSD 的寿命可以粗略估算:

白板纸每张最多擦一千多次,听起来少,但只要管理员均匀使用,一整面白板能写几十年。寿命的关键不是“擦了多少次”,而是“让每张纸都被均匀地擦”。

L=PE×CuserDdaily×WA×365\displaystyle L = \frac{PE \times C_{user}}{D_{daily} \times WA \times 365}
寿命(年)= 擦写次数 × 容量 ÷(每日写入量 × 写放大 × 365)。TLC 白板纸虽然每张只耐擦约一千多次,但只要数据量不大、写入不放大,照样用很多年。
  1. 擦写寿命 $PE$NAND 每个单元可擦写次数上限,SLC 约 10 万次、MLC 约 1 万次、TLC 约 1000–3000 次。
    PETLC≈1000∼3000\displaystyle PE_{\text{TLC}} \approx 1000 \sim 3000
  2. 用户容量 $C_{user}$SSD 标称容量,容量越大,可反复擦写的总字节数越多。
    Cuser=标称容量(GB)\displaystyle C_{user} = \text{标称容量(GB)}
  3. 每日写入量 $D_{daily}$应用平均每天写进盘的数据量,单位 GB/天,写得越猛寿命越短。
    Ddaily=每天写入字节数1 GB\displaystyle D_{daily} = \frac{\text{每天写入字节数}}{1\ \mathrm{GB}}
  4. 写放大 $WA$实际写闪存的物理数据量与用户逻辑写入量的比值,大于 1 说明内部做了额外写操作,是寿命的隐形杀手。
    WA=DflashDhost\displaystyle WA = \frac{D_{flash}}{D_{host}}

FTL地址映射

现在问题来了:你明明告诉管理员“把数据写到第 10 格”,可管理员为了让白板不被乱擦,常常把内容塞到第 37 格的空白处。等你下次来取,报的还是第 10 格的编号,管理员翻开那本厚厚的地址簿,查出真实位置:哦,在 37 号。这本簿子,就是 FTL。

FTL(Flash Translation Layer)是 SSD 主控里的一层软件:主机永远只认识“逻辑地址 LBA”,而闪存物理上只有散落的“物理页 PBA”。每次写入,FTL 找一个空闲物理页写进去,再更新映射表;每次读取,FTL 查表把 LBA 翻译成 PBA。这一层把“先擦后写、整块擦除”的物理约束,包装成了一个“随便覆盖随便写”的普通磁盘交给操作系统。

地址映射带来巨大收益:随机写不再需要等待擦除,而是“写到空闲页、旧页标记失效”,等效地把随机写变成了闪存喜欢的顺序流向。但它也有代价——旧数据占用的空间越积越多,必须靠垃圾回收清理,于是出现了写放大:

地址簿越厚,管理员整理起来越累。映射表要占内存,随机小写每次都要更新映射;而最关键的是,簿子上那些“已失效”却还没被橡皮擦掉的记录,正是后面 TRIM 和 GC 登场的理由。

WA=DflashDhost\displaystyle WA = \frac{D_{flash}}{D_{host}}
写放大 = 实际写入闪存的数据量 ÷ 主机请求写入的数据量。比值越大,闪存被“无效劳动”拖累得越狠。
  1. 物理写入量 $D_{flash}$主控实际写进闪存的页数,包含主机要求的写入,也包括垃圾回收搬运时的重复写入。
    Dflash=主机写入页数+GC 搬运页数\displaystyle D_{flash} = \text{主机写入页数} + \text{GC 搬运页数}
  2. 逻辑写入量 $D_{host}$主机真正请求写入的数据量,是用户视角的“有效劳动”。
    Dhost=主机写入页数\displaystyle D_{host} = \text{主机写入页数}
  3. $WA > 1$ 的含义物理写入超过逻辑写入的部分,就是“搬了又搬”造成的额外损耗,写放大越大,寿命越短、性能越差。
    WA=DflashDhost≥1\displaystyle WA = \frac{D_{flash}}{D_{host}} \geq 1

TRIM与垃圾回收

一天,读者小明对管理员说:“那本《旧算法导论》我不要了。”管理员点点头,翻开地址簿,在那条记录旁写了个大大的“废”字——这就是 TRIM。然后深夜闭馆后,管理员把这本书所在的那张纸(块)翻出来,把上面值得留的书逐本抄到别处空白页,然后拿起橡皮,嚓——整块擦干净。这就是垃圾回收(GC)。

关键区别在于:如果小明不打招呼(文件系统不支持 TRIM),管理员根本不知道那本书已经作废,深夜整理时还会把它和其他好书一起搬走、继续占用白板。“文件已删除”只是文件系统知道的事,SSD 主控看不见。TRIM(SATA 上叫 ATA TRIM,NVMe 上叫 Deallocate)就是操作系统主动告诉主控:“这些逻辑地址的旧数据已经没用了,随时可以回收。”

GC 时,一个块里有效数据占比越高,搬移越亏:搬 $r$ 页有效数据,只能腾出 $1-r$ 页空间,下一轮还得再搬。这个“搬了又搬”直接把写入量放大:

所以聪明的管理员最喜欢两件事:一是你提前打招呼(TRIM),让它知道哪些是垃圾不用搬;二是白板上永远留几块空地(OP),方便深夜搬家。否则等到白天你急着写字时才现擦现腾,白板就卡得让人抓狂。

WA=11−r\displaystyle WA = \frac{1}{1 - r}
当待回收块中有效数据占 $r$ 时,写放大约为 $1/(1-r)$。有效数据越少(垃圾越多),回收越划算;这就是 TRIM 的价值——提前把“失效页”变成“垃圾页”。
  1. 有效页占比 $r$待回收块里仍有用数据占整块的比例。
    r=块内有效页数块内总页数\displaystyle r = \frac{\text{块内有效页数}}{\text{块内总页数}}
  2. 可释放比例 $1 - r$块里“垃圾页”的占比,也是每轮回收真正腾出的空间比例。
    1−r=块内无效页数块内总页数\displaystyle 1 - r = \frac{\text{块内无效页数}}{\text{块内总页数}}
  3. 倒数关系搬 $r$ 页有效数据只换来 $1-r$ 页新空间,总写入被放大为 $1/(1-r)$ 倍。无 TRIM 时已删除数据也被当成有效数据,$r$ 偏高,写放大飙升。
    WA=11−r⇒r=0.5 时 WA=2\displaystyle WA = \frac{1}{1-r} \Rightarrow r=0.5\ \text{时}\ WA=2

磨损均衡与OP

白板用久了,管理员发现规律:总有几个角落天天被写、被擦,纸都快擦穿了;另一些角落却常年空着,纸崭新。于是他定了规矩:新内容优先写到“被擦得最少”的区域(动态磨损均衡);实在不够,就把那些冷冰冰、永远不动的“长驻数据”搬到磨损重的纸上,把新纸腾出来给活跃数据用(静态磨损均衡)。

磨损均衡(Wear Leveling)的目的,就是让所有块擦写次数尽量接近,避免少数块先报废成为坏块。动态均衡只管分配新页时的“一碗水端平”,但静态数据若一直占着低磨损块,均衡就无从谈起,所以需要静态均衡主动搬动冷数据来“补短板”。

另一张底牌是 OP(Over-Provisioning)预留空间:一块标称 1TB 的盘,实际 NAND 容量可能是 1.07TB 甚至更多,多余部分用户看不见、不参与地址映射,只用来做 GC 的周转仓库、坏块替换和磨损均衡的机动兵力。OP 越多,写放大越低,寿命越长:

管理员藏起来的备用白板,外人看不见,却是整间图书馆稳定运转的底气:深夜 GC 有地方搬家,个别纸写坏了能悄悄换掉,用户白天永远感受不到这些惊心动魄。

L=PE×Cuser×(1+OP)Ddaily×WA×365\displaystyle L = \frac{PE \times C_{user} \times (1 + OP)}{D_{daily} \times WA \times 365}
预留空间让“物理可擦写总量”比用户可见容量多出 $OP$ 倍,白板纸总数变多、周转余地变大,寿命和稳定性能同时受益。
  1. 物理容量因子 $(1+OP)$预留空间让物理可擦写容量大于用户可见容量,分母不变、分子变大,寿命提升。
    Cphysical=Cuser×(1+OP)\displaystyle C_{physical} = C_{user} \times (1 + OP)
  2. 预留空间占比 $OP$隐藏容量占用户可见容量的比例,企业级盘常见 7%–28%。
    OP=Cphysical−CuserCuser\displaystyle OP = \frac{C_{physical} - C_{user}}{C_{user}}
  3. 与无 OP 公式对比相比基础寿命公式,这里多了 $(1+OP)$ 的乘积;更大的周转空间还能降低 GC 频率,间接压低 $WA$。
    L=PE×CphysicalDdaily×WA×365\displaystyle L = \frac{PE \times C_{physical}}{D_{daily} \times WA \times 365}

性能指标对比

现在把两套设备放在一起考试。第一题:随机访问——“马上给我第 1000 格的数据。”唱片机要先抖一下唱臂、等半圈唱片,10 毫秒过去了;白板呢?主控查一下地址簿,微秒级就报出答案。第二题:从头到尾读 1GB——唱片机的连续读其实不慢,白板的连续读也只是略微领先。

存储设备的“随机性能”和“顺序性能”是两回事。随机性能用 IOPS 衡量,即每秒能完成多少次独立小读写;延迟则是一次 IO 的耗时。HDD 的随机 IO 被寻道加旋转锁死在百 IOPS 量级,SSD 依靠并行闪存通道轻松上万。两者的换算关系很直接:

顺序吞吐上两者差距缩小:HDD 约 200MB/s,SATA SSD 约 550MB/s,NVMe SSD 可达数千 MB/s;但 HDD 的优势在每 GB 成本和冷数据归档。功耗、噪音、抗震方面,HDD 有电机和磁臂,怕摔、费电、嗡嗡响;SSD 无机械件,安静、抗震动、功耗低。寿命上,HDD 是机械磨损(MTBF),SSD 是擦写磨损(P/E 周期)。

一个适合“从头放到尾”的连续世界,一个擅长“指哪打哪”的随机战场。选存储,先回答一个问题:你的数据是被一遍遍从头读到尾,还是被无数并发任务随机点杀?

IOPS=1000Lms\displaystyle IOPS = \frac{1000}{L_{ms}}
每秒能完成的 IO 次数 = 1000 ÷ 单次 IO 的平均延迟(毫秒)。延迟越短,IOPS 越高,两者成反比。
  1. 平均延迟 $L_{ms}$一次随机 IO 从发出到完成的平均耗时,单位毫秒。
    Lms=总耗时IO 次数\displaystyle L_{ms} = \frac{\text{总耗时}}{\text{IO 次数}}
  2. 毫秒换算1 秒 = 1000 毫秒,每秒能完成 $1000 / L_{ms}$ 次 IO。
    IOPS=1000Lms\displaystyle IOPS = \frac{1000}{L_{ms}}
  3. 代入对比HDD 延迟约 10ms 得 100 IOPS;SSD 延迟约 0.1ms 得 10000 IOPS,差距百倍。
    IOPSSSDIOPSHDD≈100.1=100\displaystyle \frac{IOPS_{SSD}}{IOPS_{HDD}} \approx \frac{10}{0.1} = 100

存储选型策略

图书馆馆长最终拍板:不分好坏,只分场合。老唱片机(HDD)买来当“仓库”——几年都未必翻一次的归档书,堆在架子上,便宜、容量大;白板(SSD)摆在“前台”——借阅量最高的热门书,必须随叫随到。中间再放一小块白板当缓存,把热门书先抄过来。

选型第一原则是匹配负载特征:随机小 I/O 密集的场景(数据库 OLTP、消息队列、AI 训练的数据读取)用 SSD;顺序大块读写(日志归档、视频监控、冷备份)用 HDD 性价比更高。第二原则是容量与成本:HDD 每 TB 成本远低于 SSD,但单位 IOPS 成本 SSD 完胜。衡量写入压力的一个常用指标是 DWPD:

企业里常见“混合分层”:分布式存储(如 Ceph)用 SSD 做缓存层或元数据盘,冷数据落 HDD;数据库用 NVMe 跑热数据、把历史分区迁到 HDD。买设备前先摸清自己图书馆的“读者画像”:读多写少、随机为王的,把预算押在白板区;写多读少、顺序存档的,让老唱片机多扛几年。设备没有好坏,只有合不合适。

DWPD=DdailyCuser\displaystyle DWPD = \frac{D_{daily}}{C_{user}}
每日全盘写入次数 = 每天写入量 ÷ 盘容量。DWPD 越高,说明盘每天被“重写”的遍数越多,对耐久等级的要求也越高。
  1. 每日写入量 $D_{daily}$工作负载一天产生多少 GB 写入,由应用决定。
    Ddaily=每天写入的数据量(GB)1 天\displaystyle D_{daily} = \frac{\text{每天写入的数据量(GB)}}{\text{1 天}}
  2. 用户容量 $C_{user}$单盘的用户可用容量(GB)。
    Cuser=单盘标称容量\displaystyle C_{user} = \text{单盘标称容量}
  3. 比值含义DWPD > 1 表示每天写满全盘不止一遍,必须选企业级高耐久盘;读多写少的应用选低 DWPD 的消费级盘即可。
    DWPD=DdailyCuser\displaystyle DWPD = \frac{D_{daily}}{C_{user}}

AI 应用场景

数据库 OLTP 的随机写优化

阶段:数据存储与访问优化

解决的问题:训练样本库、特征服务背后通常是 MySQL/PostgreSQL 这类 OLTP 数据库,随机小写密集。传统 HDD 每次写都要寻道加旋转,延迟 10ms 级;改用 NVMe SSD 后延迟降到百微秒级,配合定期 fstrim,避免 GC 堆积导致的性能滑坡。

具体案例:某推荐系统团队把特征数据库迁到 SSD,并设置每周执行一次 TRIM,随机写 IOPS 从 150 提升到 12000,训练样本落库不再成为流水线瓶颈。

分布式存储的分层与寿命监控

阶段:大规模数据湖与模型资产管理

解决的问题:Ceph 等分布式存储里,海量冷数据(历史实验日志、旧模型权重)放在 HDD 上成本极低,但热数据(当前训练集、checkpoint)需要 SSD 的高速随机读。通过缓存分层把两者组合,并监控 SSD 磨损与写放大,避免“热盘提前报废”。

具体案例:一家 AI 公司的训练集群用 4 块 NVMe SSD 做缓存层、20 块 HDD 做容量层,冷热数据自动迁移,存储成本下降 60%,同时用 SMART 指标预测 SSD 剩余寿命,提前更换。

云主机云盘的 TRIM 与空间回收

阶段:云基础设施的资源治理

解决的问题:云服务器删除大文件后,若不执行 TRIM,云盘主控仍把已删除数据当作有效数据,GC 时白白搬运,导致写放大和性能衰减。云厂商在 Linux 虚拟机里启用定期 fstrim,并针对持久化云盘监控 DWPD 指标。

具体案例:某云上跑 AI 推理服务,每两天删除一批过期模型缓存。运维配置 cron 任务每周 fstrim -v /data,实测云盘随机写延迟恢复稳定,SSD 寿命评估从 1.2 年延长到 2 年以上。

Python 演示

python104 行
import random class SSDSim:    """模拟SSD主体结构: 块-页, FTL映射, TRIM, 垃圾回收, 统计写放大"""     def __init__(self, blocks=16, pages=4, op_area=0.25):        self.blocks = blocks        self.pages = pages        self.physical = blocks * pages        self.user_capacity = int(self.physical * (1 - op_area))  # 用户可见容量        self.grid = [['free' for _ in range(pages)] for _ in range(blocks)]        self.logical_pages = {}   # FTL映射表: LBA -> (block, page)        self.host_writes = 0      # 主机请求写入页数        self.physical_writes = 0  # 实际写入闪存页数, 含GC搬运     def _free_count(self):        return sum(1 for b in range(self.blocks) for p in range(self.pages)                   if self.grid[b][p] == 'free')     def _find_free(self):        for b in range(self.blocks):            for p in range(self.pages):                if self.grid[b][p] == 'free':                    return (b, p)        return None     def write(self, lba):        # 主机写一个逻辑页        self.host_writes += 1        if self._free_count() < self.pages:            self.gc()  # 预留空间不足时先触发垃圾回收        pos = self._find_free()        if pos is None:            raise RuntimeError('no free page')        b, p = pos        if lba in self.logical_pages:            ob, op = self.logical_pages[lba]            self.grid[ob][op] = 'dirty'  # 旧数据变脏页        self.grid[b][p] = 'valid'        self.logical_pages[lba] = (b, p)        self.physical_writes += 1     def trim(self, lba):        # TRIM: 文件系统提前通知SSD该逻辑页已删除        if lba in self.logical_pages:            b, p = self.logical_pages.pop(lba)            self.grid[b][p] = 'free'     def gc(self):        # 垃圾回收: 选有效页最少的块, 搬运有效页后整块擦除        b = self._pick_block()        moves = []        for p in range(self.pages):            if self.grid[b][p] == 'valid':                nb, np = self._find_free()                self.grid[nb][np] = 'valid'                moves.append((p, nb, np))                self.physical_writes += 1  # 搬运造成写放大        for p in range(self.pages):            self.grid[b][p] = 'free'        for lba, loc in list(self.logical_pages.items()):            ob, op = loc            if ob == b:                for op_, nb, np in moves:                    if op_ == op:                        self.logical_pages[lba] = (nb, np)                        break     def _pick_block(self):        best, best_cnt = 0, self.pages + 1        for b in range(self.blocks):            cnt = sum(1 for p in range(self.pages) if self.grid[b][p] == 'valid')            if cnt < best_cnt:                best, best_cnt = b, cnt        return best     @property    def write_amplification(self):        return self.physical_writes / self.host_writes if self.host_writes else 0.0  def main():    random.seed(7)    sim = SSDSim(blocks=8, pages=4, op_area=0.2)    lba_count = 12    print('== SSD写放大模拟 ==')    for op in range(60):        if random.random() < 0.3:            lba = random.randint(0, lba_count - 1)            sim.trim(lba)            action = 'TRIM'        else:            lba = random.randint(0, lba_count - 1)            sim.write(lba)            action = 'WRITE'        if op % 10 == 9:            print('op %2d: %-5s host=%3d flash=%3d WA=%.2f' % (                op + 1, action, sim.host_writes, sim.physical_writes,                sim.write_amplification))    print('最终写放大: %.2f' % sim.write_amplification)  if __name__ == '__main__':    main()
目的:用 30 行核心逻辑模拟 SSD 的页写块擦、FTL 映射、TRIM 与垃圾回收,并量化“写放大”这一关键指标。
  1. self.logical_pages = {}模拟 FTL 映射表,记录逻辑地址 LBA 对应的物理页位置,相当于管理员的地址簿。
  2. if lba in self.logical_pages:覆盖写时,旧页不直接擦除,而是标记为 dirty,这就是“先写到新页、旧页失效”。
  3. def trim(self, lba):模拟 TRIM:文件系统主动告知某逻辑页已删除,直接将该页标为 free,避免了 GC 搬运垃圾。
  4. self.physical_writes += 1每次 GC 搬运有效页都计入物理写入,用来统计写放大。
  5. if self._free_count() < self.pages:空闲页不足时触发 GC,模拟预留空间耗尽前的回收动作。
  6. self._pick_block()贪心选择有效页最少的块回收,符合真实 SSD 的 GC 策略。
  7. write_amplification计算 物理写入 / 逻辑写入,验证 TRIM 与 OP 对写放大的抑制效果。

常见误区

误区我在系统里把文件删掉了,SSD 上的数据应该马上没了,空间立刻能重新使用。

正解删除只是文件系统在元数据里做了标记,SSD 主控完全不知道这件事。如果没有 TRIM,主控在 GC 时还会把这些“已删除”的数据当成有效数据搬来搬去,白板上的垃圾被管理员当宝贝打包。必须由操作系统发 TRIM 命令通知主控,空间才会真正进入可回收队列。

为何常见:我们习惯了 HDD 的删除模型——磁头可以直接覆盖扇区,无需被告知;而 SSD 是“先擦后写”的介质,信息不对称导致误判。

误区执行 TRIM 后,数据马上被物理擦掉,所以固态硬盘删除后就一点痕迹都没有了。

正解TRIM 只是让相关页进入“无效可回收”状态,真正的物理擦除发生在后台 GC 或空闲时,不是瞬间动作。在擦除真正发生之前,部分残留数据仍可能被专业工具恢复;反之,数据安全需要的是加密盘或 Secure Erase,而非依赖 TRIM。

为何常见:把“文件系统层的删除”和“闪存物理层的擦除”混为一谈,忽略了 GC 的异步时序。

误区SSD 没有寻道,所以不管盘多满、写多久,速度永远一样快。

正解当空闲页不足时,主控必须现场执行 GC:搬运有效数据、整块擦除,写入延迟会骤然升高,这就是“满盘掉速”。预留空间 OP 和 TRIM 正是为了把 GC 摊到后台,维持稳态性能。白板写满后深夜才擦,白天急着写自然要等。

为何常见:只看到 SSD 无机械运动的优势,忽略了“页写块擦”的固有不对称和后台整理的开销。

误区硬盘转速一样,不管读哪个扇区,速度不都差不多嘛?

正解顺序读时磁头几乎不用大幅移动,持续带宽可达 200MB/s;随机小 IO 每次都要面对 5–10ms 寻道加 4ms 旋转延迟,实际只有百余 IOPS。所以“从头放到尾”和“来回跳歌”对唱片机是完全不同的体验。

为何常见:把“接口速率”和“实际寻址开销”混淆,忽视了机械运动是 HDD 最大的时间成本。

误区TLC 只有一千多次擦写寿命,我天天写数据,SSD 不是很快就要坏了?

正解寿命公式是 擦写次数 × 容量 ÷ 每日写入量 ÷ 写放大。一块 1TB 的盘即使每天写 100GB,也要 20 年以上才触及 TLC 的寿命上限;再加上磨损均衡让所有块均匀消耗、OP 提供冗余替换,正常使用下 SSD 寿命通常远超整机换代周期。

为何常见:只看到“擦写次数上限”这个分母,忽略了容量这个巨大的分子,也没有考虑磨损均衡的兜底作用。

面试题

Q1. HDD 随机读性能远低于顺序读,最主要原因是什么?

单选题 · easy

  • A.盘片转速随机波动
  • B.磁头要机械寻道并等待旋转← 正确答案
  • C.硬盘接口带宽不足
  • D.缓存容量太小

答案:B。随机读时盘片转速是恒定的,接口带宽与缓存也不是主要瓶颈;关键在于每次随机访问都要磁头横移(寻道)加等待目标扇区转过来(旋转延迟),这两项机械开销让随机 IO 慢到百 IOPS 量级。

Q2. NAND 闪存的写入和擦除单位分别是什么?

单选题 · easy

  • A.页、块← 正确答案
  • B.块、页
  • C.扇区、页
  • D.位、字节

答案:A。NAND 的最小读写单位是页(page),最小擦除单位是块(block),一个块包含许多页。这正是“页写块擦”的固有不对称,也是 FTL、TRIM、GC 存在的根本原因。

Q3. 一块 7200 RPM 的硬盘,平均旋转延迟约为多少?

单选题 · mid

  • A.2.08ms
  • B.4.17ms← 正确答案
  • C.8.33ms
  • D.16.67ms

答案:B。转速 7200RPM 表示每秒转 120 圈,转一圈耗时 60/7200 ≈ 8.33ms;平均旋转延迟是转半圈的时间,即约 4.17ms。这种“等半圈”的开销是随机 IO 性能低下的主因之一。

Q4. 关于写放大 WA,下列描述正确的是?

单选题 · mid

  • A.WA = 主机请求写入量 / 实际写入闪存量
  • B.WA = 实际写入闪存量 / 主机请求写入量← 正确答案
  • C.WA 总是小于 1
  • D.WA 只与接口协议有关

答案:B。写放大 = 实际写入闪存的数据量 ÷ 主机请求写入的数据量,一般大于等于 1。GC 搬运有效页、磨损均衡搬数据都会产生“额外写入”,所以 WA 反映的是闪存被无效劳动拖累的程度,与接口协议关系不大。

Q5. TRIM 命令的主要作用是什么?

单选题 · mid

  • A.立即物理擦除用户数据以保证安全
  • B.压缩用户写入的数据
  • C.提前告知 SSD 哪些页无效,减少 GC 搬运← 正确答案
  • D.提高 SSD 的写入电压

答案:C。TRIM 由操作系统/文件系统发起,通知 SSD 某些逻辑页的数据已删除、可视为无效。这样 GC 时就不会把“垃圾”当有效数据搬运,从而降低写放大、维持写入性能。它不负责立即物理擦除,也不压缩数据。

Q6. 下列哪些情况会显著增加 SSD 的写放大?

多选题 · mid

  • A.大量小文件随机写入← 正确答案
  • B.盘内空闲空间很少← 正确答案
  • C.文件系统不支持 TRIM← 正确答案
  • D.持续大文件顺序写入

答案:ABC。随机小写容易制造碎片、频繁触发 GC;空闲空间少意味着 GC 周转余地小、回收频率高;无 TRIM 时已删除数据被当成有效数据搬运。持续大文件顺序写则接近理想情况,写放大接近 1,不会显著增大。

Q7. 某块中有效数据占 50%,回收该块导致的近似写放大是多少?

单选题 · hard

  • A.1.0
  • B.1.5
  • C.2.0← 正确答案
  • D.4.0

答案:C。写放大 ≈ 1/(1-r) = 1/(1-0.5) = 2。搬 0.5 页有效数据只换来 0.5 页新空间,凑满 1 页新写入需要回收两轮,总物理写入是逻辑写入的两倍。有效数据占比越高,写放大越大。

Q8. 关于磨损均衡与预留空间 OP,下列说法正确的有?

多选题 · hard

  • A.动态磨损均衡优先写入擦写次数少的块← 正确答案
  • B.静态磨损均衡会把冷数据搬到高磨损块← 正确答案
  • C.OP 空间用户可见,用于扩展容量
  • D.合理 OP 可降低写放大、延长寿命← 正确答案

答案:ABD。动态磨损均衡在分配新页时选 P/E 次数最少的块;静态均衡主动搬走冷数据,让低磨损块参与循环;OP 是用户不可见的隐藏容量,用于 GC 周转和坏块替换,能降低写放大并延长寿命,而不是给用户扩容。

Q9. HDD 与 SSD 在随机 IOPS 上的大致差异是?

单选题 · easy

  • A.HDD 比 SSD 高约 10 倍
  • B.SSD 通常比 HDD 高 100 倍以上← 正确答案
  • C.两者基本相同
  • D.SSD 只高 2 倍

答案:B。HDD 随机 IOPS 约 100–200,SSD 轻松达到数万,差距在百倍量级。差距的根源是 HDD 每次随机 IO 都要机械寻道和旋转等待,而 SSD 使用电子寻址加上多通道并行。

Q10. 存储选型时,适合使用 HDD 的场景或考量有?

多选题 · mid

  • A.海量冷数据归档,追求每 TB 成本低← 正确答案
  • B.高并发随机小额读写 OLTP 场景
  • C.顺序大吞吐写入的视频监控存储← 正确答案
  • D.需要微秒级响应延迟的场景

答案:AC。HDD 每 TB 成本低、顺序带宽尚可,适合冷数据归档和视频这类顺序大块写入;而高并发随机小 IO 与微秒级延迟正是 SSD 的主场。选型本质是匹配负载特征:随机为王选 SSD,顺序存档选 HDD。