磁盘存储 入门

服务器把 curl https://api.example.com/user/42 解完密了。 现在有两件事要跟物理设备说话:OS 给 access.log 追加一行, 数据库要把 user row 42 从它住的介质上读出来。两件事都付设备物理的代价 —— IOPS、写放大、队列深度、重建时间。4 个 section 把画面搭出来:物理(HDD vs SSD vs NVMe、IOPS vs 吞吐 vs 延迟);SSD 内部带可交互的写放大走读(NAND 页、擦除块、FTL、GC);接口(SATA、SAS、NVMe、队列深度、io_uring); 以及机箱级 + 网络级的 RAID。最后是速查表

01

物理 —— HDD vs SSD vs NVMe、IOPS vs 吞吐 vs 延迟

4 KB 随机读是经典 micro-benchmark,因为几乎每个数据库查询、日志追加、小文件查找都变成它。 那次读的代价完全由文件系统下面坐的是什么决定 —— 转动的磁碟、SATA SSD、还是 PCIe 上的 NVMe。 它们之间隔着两个数量级。

现在,我们这个 curl 请求有两次写要落盘:web 服务器给 access.log追加一行 ~200 字节,后台支撑 user/42 的数据库要读几个 B-tree 页 (每页 ~4 KB)来满足查询。这些操作每次是 5 ms 还是 50 μs, 是你 provision 的物理层的函数。

HDD —— 转动的磁盘

硬盘是一摞磁碟、以 7,200 RPM(消费级)或 15,000 RPM(企业级)旋转, 一个可移动的执行臂上每面装一个读写头。要读一个块,头要 seek 到对的磁道、 然后等对的扇区转到底下。数学很残酷:

seek 时间            ~3-15 ms   (全程 vs 邻接)
旋转延迟              ~4 ms      (7200 RPM 半圈)
传输时间              ~0.05 ms   (4 KB 可忽略)
                     ──────
随机 4 KB 读          ~8-15 ms

顺序吞吐              ~150-250 MB/s
随机 IOPS             ~100-200

这就是为啥一个 200 IOPS 的 HDD 在队列深度 32 下平均每请求等 160 ms —— 你数据库的 P99 延迟就是一只动金属臂的物理。顺序读比随机快 1,000 倍, 因为头不动、旋转早对齐了。这是数据库引擎执迷于顺序 I/O 的根本原因 —— 预写日志、LSM 树压缩、批量 ETL。它们都在尝试把 IOPS-bound 的活儿变成 MB/s-bound 的。

SATA SSD —— 没动件、线还慢

SSD 用 NAND flash 芯片替代磁碟。无 seek、无旋转。一次 4 KB 随机读变成一次 NAND 编程周期(~50 μs)加一次 SATA 来回 —— 算 100 μs 总共。 SATA 线本身把你卡在 6 Gbps = 600 MB/s,不管 NAND 多快:

随机 4 KB 读             ~100 μs
顺序吞吐                 ~500-550 MB/s   (被 SATA 3.0 卡)
随机 IOPS                ~50,000-100,000

AHCI 命令队列            32 个未决操作

比 HDD 提了 500 倍 IOPS,但 SATA 协议 —— 为转盘设计的 —— 留了大量 NAND 性能在桌上。 AHCI(host controller spec)只有单一 32-深的命令队列。 现代 NAND 能服务几百个并发读;SATA 没法问它们。

NVMe —— PCIe 直接挂、每核一队

NVMe 直接坐在 PCIe 总线上。PCIe 4.0 x4 = 8 GB/s 原始带宽(PCIe 5.0 x4 = 16 GB/s)。 NVMe 协议把 AHCI 的单一 32-深队列换成最多 64K 个提交队列、每个最多 64K 深 —— 典型每 CPU 核一对队列,内核提交 I/O 时永远不在全局锁上争用:

随机 4 KB 读             ~10-50 μs
顺序吞吐                 ~7 GB/s          (PCIe 4.0 x4)
随机 IOPS                500,000 - 1.5M+

队列                     最多 64K × 64K 深

比 HDD 延迟少 100 倍、比 SATA SSD IOPS 多 10 倍。但你只在队列深度 32+ 看到这些数字 —— 单线程同步读循环即便在百万 IOPS 设备上也就到 3 万 IOPS, 因为设备在等下个请求时是闲的。这就是为啥 fio --iodepth=32 这类基准重要, 以及现代 I/O 路径(io_uring、内核旁路 SPDK)存在 —— 来真的填满那些队列。

延迟悬崖 —— 存储在层级里的位置

存储延迟只有跟 CPU 可能在等的其他东西比时才有意义:

CPU L1 缓存                ~1 ns
CPU L2 缓存                ~4 ns
CPU L3 缓存                ~15 ns
DRAM(主内存)              ~80 ns
NVMe SSD(随机读)          ~10-50 μs    ← 比 L1 慢 100,000 倍
SATA SSD(随机读)          ~100 μs
网络 SSD / EBS gp3         ~500 μs
HDD(随机读)               ~5-15 ms     ← 比 L1 慢 10,000,000 倍
S3 / 冷存储(首字节)       ~10-100 ms

两个要点。第一,缓存命中和冷盘读之间的差距大致是「走到你厨房」和「走到另一个大陆」的差距 —— 这就是为啥每个存储之上的层(Postgres shared_buffers、Redis、内核 page cache) 对缓存这么激进。第二,NVMe 和 HDD 之间的差距(1,000 倍)比 DRAM 和 NVMe 之间的差距(~500 倍)还大 —— 网络挂的 HDD 时代对任何延迟敏感的工作负载真的过去了。

IOPS vs 吞吐 —— 4 KB 时是同一个数

对小 I/O(4 KB、典型数据库页),IOPS 乘块大小等于吞吐。100,000 IOPS × 4 KB = 400 MB/s。 所以一块 SATA SSD 的「500 MB/s 顺序」规格和「100,000 随机 IOPS」规格描述的大致是同一面墙。 对大 I/O(1 MB ETL 读),吞吐主导 —— 即便 HDD 拉 250 MB/s 顺序也够。 4 KB 随机读成为经典基准的原因是它匹配最痛的现实工作负载: OLTP,其中每个用户查询都摸几个散落在数据文件里的索引页。

要点。「一次 4 KB 随机读在 HDD 上 ~5-15 ms(seek + 旋转)、 SATA SSD 上 ~100 μs、NVMe 上 ~10-50 μs。IOPS 乘块大小等于吞吐; OLTP 工作负载下 IOPS 主导。NVMe 的 ~1M IOPS 只在队列深度 32+ 出现 —— 单线程同步 I/O 留了 30 倍性能在桌上。HDD 到 NVMe 的差距(1,000 倍) 现在比 DRAM 到 NVMe 的差距(500 倍)还大。」

02

SSD 内部 —— NAND、FTL、GC、写放大

NAND flash 芯片不是「没动件的硬盘」—— 它是一种很不一样的东西、假装成硬盘。 这层伪装由 Flash Translation Layer(FTL)粘合着,它藏了 3 个怪癖: 写按页粒度但擦按块粒度、每个 cell 会磨损、设备靠维护一个 free pool 来伪造随机写性能。

NAND cell —— SLC、MLC、TLC、QLC

SSD 底部是一格 NAND cell、按技术每个存 1 到 4 bit:

  • SLC(1 bit / cell)—— 最快、~10 万次 P/E 周期、 仅企业 / 缓存用。独立形式几乎绝种。
  • MLC(2 bit / cell)—— ~1 万次 P/E 周期。多数老企业 SSD。
  • TLC(3 bit / cell)—— ~3 千次 P/E 周期。消费级和多数现代企业 SSD 的主导技术。
  • QLC(4 bit / cell)—— ~1 千次 P/E 周期。便宜密集; 用于读密集的批量存储(Samsung 870 QVO、Intel D5-P5316)。

每 cell 密度越高意味着 $/TB 越便宜,但 cell 坏前能重写的次数越少。 这就是为啥寿命评级(TBW —— 总写字节数)由厂商公布: 消费级 1 TB TLC SSD 评 ~600 TBW、企业级 NVMe 评 ~3,000+ TBW。 在那个寿命内,cell 磨损均衡和过度配置把设备粘在一起。

页和擦除块 —— 不对称

2 种粒度重要、它们不一样:

  • —— 你能编程(写)的最小单位。典型 4 KB 到 16 KB。
  • —— 你能擦除的最小单位。典型 128 KB 到 1 MB(每块 64-256 页)。

你不能就地重写一页。一页只要编程过哪怕一次,把新数据放到那个 LBA 的唯一办法是 (a)写到另一个 free 页、(b)最终擦整个块。这就是所有其他 SSD 怪事的根。

FTL —— 藏这一切的固件

Flash Translation Layer 是跑在 SSD 控制器上的软件。它干 4 件事:

  1. LBA → 物理页映射。一个大的 RAM 内表 (每 1 TB NAND 典型 ~1 GB DRAM)。host 写 LBA 42 时, FTL 挑任意 free 物理页、更新映射。
  2. 垃圾回收。free 页低时,挑一个大多 invalid 的块、 搬还 valid 的、然后擦源块。下面的 demo 里可视化。
  3. 磨损均衡。把 P/E 周期摊到所有块上、 这样没有单一块撞到 P/E 上限而其他闲着。冷数据(很少被覆盖)定期被搬走、 让用旧的块腾给热数据。
  4. 坏块管理。cell 最终磨坏、拒绝编程。 FTL 退役它们、从备用池透明替换。

写放大 —— 沉默的 IOPS 倍增器

写放大(WA)= 物理写字节数 / 逻辑写字节数。理想 1.0 (每次 host 写花一次设备写)。实践中:

顺序写、free 空间充足                       WA ≈ 1.0
混合随机 + 30% 过度配置                     WA ≈ 1.2 - 2.0
近满 SSD 上重随机写                         WA ≈ 5 - 10+
对抗性(4 KB 随机、95% 满、无 TRIM)         WA 能超 20

高 WA 重要因为两点:它按比例烧 TBW 寿命(WA 5 意味着 600 TBW 盘在 120 TB 实际 host 写后挂), 且 GC IO 跟你应用 IO 抢、设备最忙时拉高 P99 延迟。

TRIM、过度配置、怎么打 WA

有 2 个杠杆大幅降 WA:

  • TRIM / discard。OS 发给 SSD 的命令,说「这些 LBA 不再装有意义数据了」。 没它,FTL 不知道 GC 时哪些页能丢 —— 它把它们当 valid、继续搬来搬去。 现代文件系统(ext4、XFS、APFS、NTFS)在文件删除时自动发 TRIM(或通过每周 fstrim cron)。 用 lsblk --discard 检查。
  • 过度配置。预留一块 OS 不可见的 NAND。 一块卖成 960 GB 的 1 TB 原始盘有 ~7% OP;企业盘可能预留 20-50%。 OP 越多意味着总有更多 free 页可用,所以 GC 总有低成本块来回收, 所以 WA 接近 1。你可以手动给消费盘欠分区取得同样效果。

交互 —— 一次重写、21 次物理写

SSD 写放大 —— 1 次逻辑写 → 21 次物理写Step 0 —— 一个擦除块装 64 个 4 KB 页。60 valid、4 free。工作块(64 × 4 KB = 256 KB)validinvalidfree逻辑写:0物理写:0写放大:
SSD 最小可写单位是页(典型 4 KB)。最小可擦单位是块(这里 256 KB,现代 QLC 上常 1 MB+)。一旦写过,页不能就地重写 —— 只能把新数据写到别处、把老位置标 invalid 来覆盖。这就是为啥每块 SSD 固件里都自带 Flash Translation Layer。
1 / 8
NAND 可以按粒度(4 KB)编程,但只能按粒度(这里 256 KB = 64 页)擦除。 要重写一页,FTL 写到新页、把老页标 invalid。当 free 页耗尽, GC 挑最脏的块、把它还 valid 的页搬到别处、然后擦它。 搬迁 IO 对应用不可见、但对 TBW 寿命非常可见。

SMART、磨损指标、smartctl

每块 SSD 暴露 SMART(Self-Monitoring, Analysis, and Reporting Technology)页。 SSD 健康关心的字段:

# smartctl -a /dev/nvme0n1
...
Percentage Used:                    37%        ← P/E 预算用了多少
Data Units Written:                 312,500,000 [160 TB]
Power On Hours:                     8,760
Available Spare:                    98%        ← 备用块剩多少
Media and Data Integrity Errors:    0
Error Information Log Entries:      0

Percentage Used 在盘评级 TBW 上从 0 爬到 100;过 100 厂商停止保证数据完整。Available Spare 是坏块储备 —— 掉到 ~10% 以下、安排替换。 生产机群通过 Prometheus node_exporter(nvme_ 指标)绘这些图。

要点。「NAND 能编程一个 4 KB 页、但只能擦 256 KB-1 MB 块。 FTL 通过把 LBA 重映射到新鲜页、后台跑 GC 来藏这个。 GC 把还 valid 的页搬出脏块、然后擦 —— 对 OS 不可见、但对写放大(常 2-10 倍) 和 TBW 寿命非常可见。TRIM 告诉 SSD 哪些 LBA 是 free 的; 过度配置给 GC 留余地。没两者,满 SSD 上持续随机写能一个月烧穿一年评级寿命。」

03

接口 —— SATA、SAS、NVMe、队列深度、io_uring

线和 NAND 同样重要。一个百万 IOPS 的 NAND 阵列挂在 SATA 线后面就是个 10 万 IOPS 盘。 正确的问题不是「SSD 多快」—— 是「OS 多快能提交活、设备多快能排空它的队列」。 这是 host controller 规范、命令队列深度、syscall 路径的函数。

SATA —— 遗产转盘协议

SATA 3.0 顶在 6 Gbps(8b/10b 编码后 ≈ 600 MB/s), 用 AHCI 主控接口 —— 一个围绕「设备是 HDD」假设设计的规范。 AHCI 暴露一条命令队列、32 条深、核之间没有并行概念。 当设备本来一次只能处理一个 IO 时这没事。NAND 能服务几百个并发读时,AHCI 就是瓶颈。

SATA SSD 还在出货以求兼容 —— 廉价消费盘、2.5 英寸企业盘位 —— 但没新性能档硬件按 SATA 设计。如果你在数据库层看到 SATA SSD,要么是遗产、要么是成本决定。

SAS —— 企业级串行

SAS(Serial Attached SCSI)是 SATA 的企业级答案, 有 12 Gbps 和 24 Gbps 代际、双口连接给 HA(每盘接两个控制器)、 以及好得多的命令队列(256 条)。重度用于企业转盘阵列(3PAR、NetApp FAS) 和一些企业 SSD。NVMe 大幅取代了 SAS SSD,但大容量近线存储里 SAS HDD 仍占主导。

NVMe —— 为并行 NAND 设计

NVMe(Non-Volatile Memory Express)是建在 PCIe 上的新协议, 2011 年明确为 SSD 设计。它丢掉 AHCI 的假设、替换为:

  • 最多 64K 个提交队列、每个最多 64K 深。典型配置:每 CPU 核一对提交 + 完成队列。内核 NVMe 驱动维护每 CPU 队列, 这样提交 IO 永远不在全局锁上争。
  • host RAM 里的提交 / 完成 ring buffer。host 写一条 Submission Queue Entry、然后摁门铃(PCIe MMIO 写)告诉设备有新活。 设备 DMA 数据、写一条 Completion Queue Entry、要么发 MSI-X 中断、要么 host 轮询。 模型几乎完全照搬现代 NIC。
  • ~13 条强制命令(对比 SCSI 蔓延的规范)。 精简协议、精简驱动、精简固件。

线速跟 PCIe 缩放:PCIe 3.0 x4 = 4 GB/s、PCIe 4.0 x4 = 8 GB/s、PCIe 5.0 x4 = 16 GB/s。 一块现代企业 NVMe 盘(Samsung PM9A3、Intel D7-P5520)出 100 万+ 随机读 IOPS、 顺序 7 GB/s。接口不再是瓶颈 —— NAND 自己通常是。

队列深度 —— 为啥你的基准在说谎

队列深度(QD)是 host 已交给设备但还没收完成的在飞 IO 操作数。 NVMe SSD 只在 QD ≥ 32、常 QD ≥ 128 时达到评级 IOPS:

# fio --rw=randread --bs=4k --iodepth=1 ...
read: IOPS=28.4k, BW=111MiB/s, lat avg 35.1 μs

# fio --rw=randread --bs=4k --iodepth=32 ...
read: IOPS=812k, BW=3.17GiB/s, lat avg 39.4 μs

# fio --rw=randread --bs=4k --iodepth=128 ...
read: IOPS=1.04M, BW=4.07GiB/s, lat avg 122 μs

单线程同步读循环是 QD 1:提交、等、重复。在 100 万 IOPS 设备上拿到 ~3 万 IOPS —— 把设备 97% 的容量留在地板上。要拿到全数得用线程(每个 QD 1)、libaio、 或 io_uring —— 任何能让你发很多请求又不在每个上阻塞的东西。没 QD 注释的基准数字毫无意义。

io_uring —— 现代 Linux 异步路径

io_uring(2019 年 Linux 5.1 加入)给用户态一对共享 ring buffer —— Submission Queue 和 Completion Queue —— 镜像 NVMe 的提交 / 完成模型。 应用直接把 SQE 写进共享内存、可选用一次 syscall 摁门铃、读 CQE 出来, 单个 IO 永不进内核。配合 IORING_SETUP_SQPOLL(内核轮 SQ 不需要 syscall)和 IORING_OP_READ /WRITE_FIXED(预注册 buffer),结果是零 syscall、零拷贝的 IO 路径。 交易系统和现代数据库(ScyllaDB、现代 Cassandra)原生用它。 Postgres 16+ 给 I/O worker pool 采用了它。

SPDK 和内核旁路极端

SPDK(Storage Performance Development Kit、Intel 出品)再走一步: 通过 VFIO 把 NVMe 驱动整个拉到用户态、在专用核上轮询完成、跳过内核 block 层。 赢得是 ~1 μs 延迟和每设备 ~1000 万 IOPS,这在内核里实现不了。 代价是运维:你放弃通用文件系统、通用权限、通用 iostat。 被存储厂商(Pure Storage)、高频交易、少数前沿数据库使用。 对 99% 服务,内核里走 io_uring 是对的取舍。

NVMe-oF —— 网络上的 NVMe

NVMe over Fabrics 把 NVMe 的提交 / 完成语义跨网络导出: TCP 为兼容,RDMA(RoCE / InfiniBand)增加 ~5 μs 延迟。 这是现代共享存储阵列(Pure Storage FlashBlade、NetApp ASA、DigitalOcean Block Storage) 如何在线上交付类 NVMe 性能。host 视角基本跟本地 NVMe 一样 —— 同样驱动、同样队列、同样 fio 数字 —— 除了延迟是 100-500 μs 而不是 50 μs。

要点。「接口是性能故事的一部分。SATA 卡你在 600 MB/s 和 32 在飞 op; NVMe 在 8-16 GB/s 的 PCIe 上扩到 64K 队列 × 64K 深。NVMe 盘只在 QD 32+ 达到评级 IOPS; 单线程同步 IO 让设备 95% 闲着。io_uring 是现代从用户态喂这些队列、 每 op 不用 syscall 的方式;SPDK 给交易系统极端完全跳过内核。 一份『100 万 IOPS』规格表没 QD 上下文告诉你什么都不是。」

04

RAID、ZFS RAIDZ、分布式存储

盘会挂。一块 10 TB 盘 5 年内平均数据丢失时间高到任何运机群的人最终都会丢一块。 RAID、RAIDZ、分布式复制都答同一个问题 ——「在不丢字节下扛 N 个设备故障」—— 只是在不同成本 / 复杂度档。经典级别还出现在每份生产 runbook 上。

RAID 0 —— 条带、无冗余

把每次写跨 N 个盘。吞吐和 IOPS 随 N 线性扩;容量是 N × 盘大小。故障率也线性扩 —— 任一盘挂全没。用于 scratch 卷、视频剪辑、和基准数字。 永远别用在你不能从别处重做的东西上。

RAID 1 —— 镜像

2 块盘装同样数据。读能从任一盘服务(~2× 读吞吐);写到俩(无写加速)。 容量 = N / 2。扛一块盘挂。最简单的可靠性增量,OS / 启动卷上仍常见。

RAID 5 —— 条带 + 1 奇偶

把数据跨 N-1 个盘条带、把 XOR 奇偶存第 N 个(按条带轮转使没单一盘装所有奇偶)。 扛一块盘挂;故障时,重建读剩下 N-1 个盘、重算丢的数据。容量 = (N-1) × 盘大小。

隐藏代价是 RAID 5 写惩罚:单次小随机写意味着对数据块和奇偶块都做读 - 改 - 写 —— 每次逻辑写 4 次物理 IO。大顺序写惩罚消失(整条带写在 RAM 里算奇偶、一次写所有), 但 OLTP 负载一直付它。

RAID 6 —— 条带 + 2 奇偶

每条带 2 个独立奇偶块(XOR + 第二个用 Reed-Solomon 算的函数)。 扛 2 块盘同时挂。容量 = (N-2) × 盘大小。小随机写写惩罚爬到每次 6 次 IO。

为啥这现在重要:现代 10 TB 或 18 TB 盘故障后 12 到 36 小时重建, 因为重建得按盘的持续吞吐读组里每个幸存盘的每个块。 那个重建窗口里,幸存盘 100% 负载 —— 正是边缘盘最可能挂的时候。 RAID 5 重建里第二次故障 = 全没。10+ TB 盘下,RAID 6 是最低负责选择;大盘 JBOD 上 RAID 5 是在求当下次故障 postmortem。

RAID 10 —— 先镜像后条带

成对镜像盘、条带起来。容量 = N / 2(跟 RAID 1 一样)、写惩罚只是 2 (写两个镜像、无奇偶算)、重建只要从幸存伙伴那拷一块盘的数据。 OLTP 数据库的默认 —— 高 IOPS、便宜重建、可预测的故障行为。代价是容量开销(50%)。

ZFS 和 RAIDZ —— 写时复制遇上冗余

ZFS 把卷管理器、文件系统、RAID 集成进一个栈。 RAIDZ1 / Z2 / Z3 是 RAID 5 / 6 / 7 的对应,有 2 个关键改进:

  • 无写洞。经典 RAID 5/6 在写数据块和更新它奇偶之间有窗口, 崩溃会让条带不一致。ZFS 的写时复制意味着每次写原子地到新地点;没就地更新可半做。
  • 端到端校验和。每块在父块里存校验和(不在自己旁边)。 读时,ZFS 验;不一致时,从冗余重建、重写坏扇区。 周期性 zpool scrub 后台读每块、悄悄改正。

btrfs 目标类似;两者都是「我想我的文件系统在盘骗我时注意到」的现代答案。

Bitrot —— 为啥校验和重要

盘规格引用消费 HDD 不可恢复读错率约 1014 bit 1 次、 企业级 1015 1 次。1014 bit = 12.5 TB。 所以统计上,一头到另一头扫一个 12 TB 盘期望产 1 次悄悄腐败读。 60 TB RAID 6 阵列每周 scrub 时,你一周遇 ~5 次悄悄腐败 —— 没校验和不可见、落到元数据块或关键数据库行上是毁灭性的。 ZFS 抓它们;MD RAID 上 ext4 不抓。

分布式存储 —— 网络规模的 RAID

同样思想在机架和数据中心规模:

  • 复制 —— 每块在 N 台机器上 N 副本(常 N=3)。机器粒度的 RAID 1。 扛 N-1 台机器挂。HDFS(默认 3 副本)、Ceph(replicated pool)、Cassandra(复制因子)用。
  • 纠删码 —— Reed-Solomon (k, m) 把每对象切成 k 个数据片 + m 个奇偶片, 扛 m 个同时片挂、只用 (k+m)/k 开销。S3 用这个(Reed-Solomon 约 1.8× 开销给 11 个 9 的耐久性), Ceph erasure-coded pool、MinIO、Backblaze Vault 也用。比三副本便宜;重建 CPU 代价。
  • 仲裁 —— 读或写时,联系多数副本(R + W > N)。 让你按操作调一致性 / 可用性取舍。Dynamo 论文的大洞见; 驱 Cassandra、DynamoDB、现代 Redis 复制。

跟 RAID 同样原则 —— N 副本而不是 N 盘、网络链而不是 SATA 线、 机器故障而不是 head crash。数学(奇偶、纠删码、仲裁)一样;故障域是机架或区域。

要点。「RAID 0 为速度条带、RAID 1 为安全镜像、 RAID 5/6 加奇偶给容量高效的冗余、RAID 10 先镜像后条带给 OLTP。 10+ TB 盘下,RAID 5 危险因为重建要一天、压幸存盘; RAID 6(或 RAIDZ2)是最低负责选择。ZFS / btrfs 加校验和打沉默 bitrot —— 1014 URE 率下多 TB 阵列没它们保证腐败。 网络规模上同样思想以复制、纠删码、仲裁读写出现。」

05

速查表

6 道值得能冷讲清楚的核心问题、5 个 code review 时一眼看出来的红旗。

HDD 上随机 IO 为啥比顺序 IO 慢这么多、SSD 怎么改变这个?

HDD 上,每次随机读付 seek 时间(3-15 ms 头移到对的磁道)和旋转延迟 (平均 ~4 ms 等对的扇区)。顺序读两个都不付 —— 头已经位、扇区按盘速流过去。 这就是为啥 ~100-200 IOPS 随机但 ~200 MB/s 顺序是同一块盘。 SSD 无动件、无旋转,所以随机读 ~100 μs(SATA)或 ~10-50 μs(NVMe)。 随机和顺序的差距从 1000× 塌到大约 1× —— 只剩 NAND 编程时间。 这就是 SSD 上数据库工作负载看起来这么不一样的全部原因。

写放大是什么、什么让它变坏?

写放大 = 写到 NAND 的物理字节 / host 写的逻辑字节。理想 1.0。 GC 要在擦前把脏块里还 valid 的页搬出去时它涨 —— free 页工作集越小、每次写要搬越多。 近满 SSD 上没 TRIM 的随机写能把 WA 推过 10。重要因为(a)按比例烧 TBW 寿命 —— WA 5 把你评级盘寿命砍 5 倍 —— 且(b)GC 读 / 写跟应用 IO 争、负载最高时拉高 P99 延迟。 用 TRIM、过度配置、顺序写模式打它。

编程模型角度,NVMe 和 SATA 的区别是什么?

SATA / AHCI 暴露一条命令队列、32 条深、所有 CPU 共享 —— 提交 IO 在全局锁上争、 顶在 ~10 万 IOPS 不管 NAND 多快。NVMe 暴露最多 64K 个提交队列、每个最多 64K 深、 典型每 CPU 核一对队列 —— 提交无锁、硬件多路复用、扩到 100 万+ IOPS。 但你只在队列深度 ≥ 32 看到那些 IOPS。单线程同步读代码在 100 万 IOPS 设备上拿 ~3 万 IOPS —— 要真用上并行得用 libaio、io_uring、或 worker 线程。

为啥现代大盘 RAID 5 危险?

重建时间。10-18 TB 盘故障后 12-36 小时重建 —— 重建按盘的持续吞吐读每个幸存盘的每个块。 那个窗口里幸存盘 100% 负载,正是边缘盘最可能挂的时候。~1014 URE 率下, 重建期间撞到不可恢复读的概率也不可忽视。RAID 5 扛一次故障; 重建期间第二次故障 = 全没。10+ TB 盘下,RAID 6(2 块奇偶盘)是最低负责选择。 大盘 JBOD 上 RAID 5 是在求当下次故障 postmortem。

Bitrot 是什么、什么防它?

Bitrot = 沉默数据腐败:盘读时返错字节没任何错误码。 盘规格引用消费盘约 1014 bit 1 次不可恢复读错率(~12.5 TB)。 多 TB 阵列上每个 scrub 周期统计上撞几次沉默腐败。防御: 端到端校验和(ZFS、btrfs、每个现代对象存储)存离数据远的地方, 这样两者同步腐败的几率基本是零,加后台 scrub 读每块、不一致时从冗余重建。 MD RAID 上 ext4 不防 bitrot —— 它会乐意地服务腐败数据,因为读「成功了」。

什么时候用裸 NVMe + io_uring vs. 文件系统?

裸 NVMe(或块设备上 O_DIRECT)在你想拥有缓存、分配、IO 调度时有意义: 数据库(Postgres O_DIRECT 模式、Oracle ASM、ScyllaDB)、亚 100 μs 延迟目标的交易系统、 存储产品(Pure Storage、MinIO)。赢得:无双重缓存、可预测内存压力、对 QD 和 IO 模式全控。 代价:你重实现缓存、对齐、恢复 —— 且你放弃 lsdu、 快照、备份。对 99% 服务,文件系统(NVMe 上 XFS 或 ext4)加 page cache 比你一个季度能重造的快。

Code review 时的 5 个红旗

  1. dd if=/dev/zero 基准。很多 SSD 检测全零数据、在写前压缩或去重 —— 你的「6 GB/s」数字测的是压缩器、不是 NAND。 用 fio--rw=randwrite --refill_buffers/dev/urandom 种的数据。
  2. fio --iodepth=1 数字做容量规划。一次 100 万 IOPS 设备上 3 万 IOPS 的 QD-1 测量告诉你 syscall 开销、不是盘。 生产流量从许多并发客户端自然有队列深度 —— 你的基准要一样。
  3. ≥10 TB 盘 JBOD 上 RAID 5。重建时间、URE 数学、对幸存盘的负载,都合谋让「重建期间第二次故障」成为可能结果。 那个容量档至少用 RAID 6 或 RAIDZ2。
  4. 因为「我们用 SSD」就 commit 时跳过 fsync存储介质从来没决定持久性 —— 持久性关于 volatile DRAM 缓存(page cache、SSD 控制器缓存) 和持久 NAND 之间的断电边界。没 fsync,崩溃丢最后 5-30 秒的写, 不管盘是转盘还是 PCIe 5.0 NVMe。
  5. 没 QD 或工作负载混比规格表 IOPS 就比 SSD。两块都声称「100 万 IOPS」的盘在你实际工作负载下能差 10×、看读写比、块大小、 队列深度、填充水平、TRIM 是否接上。永远用像生产的工作负载来基准。