Linux 磁盘读写放大的成因

7次阅读

根本原因是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/sdaggpt)→ 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