Linux 磁盘读写放大的成因

17次阅读

根本原因是 SSD 以 4KB page 为最小读写单位,小写需读 - 改 - 写整页,导致 IO 放大;机械盘则受限于 sector(512B 或 4KB)对齐要求。

Linux 磁盘读写放大的成因

为什么 写 1 个 字节,磁盘实际要写 4KB?

根本原因在于硬件层的最小操作单元限制:SSD 以page(通常 4KB)为读写基本单位,机械盘则以sector(传统 512B,现多为 4K native)为准。当你发起一个未对齐的write()——比如偏移量是 4097 字节、长度 1 字节——底层无法只刷 1 字节,必须:

  • 读出整个目标page(4KB)到 SSD 控制器缓存;
  • 在内存中合并新数据;
  • 擦除原page(SSD 不可覆写);
  • 把整页 4KB 写入新位置。

这 1 次逻辑写,触发了 1 次读 + 1 次擦 + 1 次写 → 实际 IO 放大 3 倍以上。更糟的是,若该 page 跨两个物理erase block,还可能引发额外迁移,进一步放大。

文件系统和分区不对齐,会雪上加霜

即使应用层写请求对齐,如果底层布局没对齐,照样放大。典型场景:

  • 分区起始扇区不是 4096 字节(8×512B)的整数倍 → fdisk -lStart 列,若不是 8 的倍数,就是错的;
  • ext4 格式化时没指定 -E stride=128,stripe-width=256 等 RAID 感知参数,导致元数据分布打乱对齐;
  • LVM 物理扩展 PE 大小(默认 4MB)与 SSD pageerase block 不匹配,中间多一层映射损耗。

结果是:你明明用 O_DIRECT 写了 4KB 对齐 buffer,内核下发到底层的 bio 却仍被拆成多个非对齐 request。

iostat -x里哪些指标暴露了读写放大?

光看 wkB/sw/s不够,关键要看放大比:

  • 计算平均写大小:avgqu-sz / (w/s) 或直接看avgrq-sz(单位扇区);若长期(即
  • 对比 r/sw/s:SSD 负载下 r/s ≪ w/srkB/s ≈ wkB/s,大概率在后台 GC 或 rewrite;
  • 观察 %util 高但 await 异常飙升(如>50ms):说明请求在队列堆积,背后常是频繁的 read-modify-write 循环。

注意:iostat不显示底层擦除次数,需结合 smartctl -a /dev/nvme0n1 | grep -i "media wear" 看 SSD 磨损指标佐证。

避免放大的实操底线

不是所有场景都能根治,但守住这三条能拦住 80% 问题:

  • 新建分区时强制 4K 对齐:fdisk /dev/sdag(GPT)→ n → 回车让起始扇区默认从 2048 开始(=1MB 对齐);
  • SSD 上禁用 barrierjournal开销(仅限数据盘):mkfs.ext4 -O ^has_journal /dev/sda1,挂载加noatime,nodiratime,discard
  • 应用写文件前检查 buffer 地址和 offset:posix_memalign(&buf, 4096, size) + lseek(fd, offset & ~4095, SEEK_SET),再write()

真正难缠的从来不是单次写放大,而是日志型应用(如 WAL、binlog)持续小写 +fsync,这种必须配合 io_uring 提交聚合或换用 libpmem 直写持久内存——普通块设备上,对齐只是起点,不是终点。

text=ZqhQzanResources