
凌晨 3:07,香港机房的短信把我叫醒:“订单写入延迟飙升,3 分钟内失败率 2.1%”。我翻出值班本,定位到昨晚新上架的一台独立服务器:业务跑在 Docker 上,数据库是 PostgreSQL,数据盘被我临时放在一块“来不及换”的消费级 NVMe 上(无掉电保护),系统盘是SATA SSD。为了交付进度,我当时心想“先跑一天,明天换盘”。结果,峰值时段 fsync 压力一上来,这块无 PLP 的 NVMe 直接露了怯。
那一刻我决定做一轮系统化评测:NVMe 到底该做系统盘还是数据盘?分区/对齐到底多大影响?掉电保护要不要上?
环境与硬件清单(香港荔枝角机房)
| 项 | 型号/版本 | 备注 |
|---|---|---|
| 机型 | Supermicro 1U / 单路 AMD EPYC 7302P(16C/32T) | 固件已更新到厂商推荐版本 |
| 内存 | 128 GB DDR4 ECC | |
| 系统 | CentOS 7.9(3.10 内核) | tuned-profile: throughput-performance |
| NVMe(企业级) | Samsung PM9A3 1.92TB U.2(PCIe 4.0 x4) | 带 PLP(掉电保护) |
| NVMe(消费级) | WD Black SN770 1TB(PCIe 4.0 x4) | 无 PLP |
| SATA SSD | Intel S4510 960GB | 带 PLP |
| 网 | 双上联 10G 光口,BGP 多线 | 与内地业务有跨境流量 |
| 工具 | fio、nvme-cli、lsblk、parted、pgbench(PostgreSQL 13)、iostat |
说明:多数香港机房供电稳定,但我仍建议**UPS + 企业级 NVMe(带PLP)**作为数据库/日志盘的标配;跨境业务高峰时段写路径一旦被 fsync 压住,延迟直接影响成交。
评测设计:四种布局 + 三类工作负载
磁盘布局方案
A:NVMe 做系统盘,数据在 SATA SSD
适合预算有限或数据写入压力小的场景。
B:SATA SSD 做系统盘,NVMe(企业级,带PLP)做数据盘 ✅
常见且推荐的生产方案。
C:一块 NVMe 分区——前 100 GiB 做系统盘,剩余空间做数据盘
适合只有一块 NVMe 的机器,权衡成本/上架速度。
D:SATA SSD 做系统盘,NVMe(消费级,无PLP)做数据盘(临时) ⚠️
仅作对照,模拟“赶工交付/等盘到位”的风险场景。
负载模型
fio 随机读写
- 4k 随机读(QD=32,4 job)
- 4k 随机写(QD=32,4 job,不强制 fsync),看设备原生写入能力
- 4k 写 fsync=1(QD=1,单 job),模拟数据库 WAL/订单落盘
128k 顺序吞吐(读取/写入,QD=32,4 job)
- 模拟日志归档、对象文件顺序IO
pgbench(PostgreSQL 13)
- synchronous_commit=on,full_page_writes=on
- -c 32 -j 16 -T 300,scale=100
- 只更换$PGDATA与pg_wal所在盘,其他保持一致
分区与对齐:我在现场的标准化做法
目标:1 MiB 对齐、GPT、系统与数据分卷独立挂载,操作简洁可回滚。
1)识别盘与基础信息
nvme list
nvme id-ctrl /dev/nvme0n1 | egrep -i 'oncs|vwc|frmw|fna'
lsblk -d -o NAME,ROTA,RO,TYPE,SIZE,MODEL
ROTA=0 表示非旋转介质(SSD/NVMe)。
vwc 只表示存在易失写缓存,不等于有 PLP;PLP 需查数据手册或选用企业型。
2)创建 GPT + 1 MiB 对齐分区(parted)
# 以企业级 NVMe /dev/nvme0n1 为例
parted -s /dev/nvme0n1 mklabel gpt
# 方案 C:前 100GiB 为系统盘,之后为数据盘
parted -s /dev/nvme0n1 unit MiB mkpart ESP fat32 1 513
parted -s /dev/nvme0n1 set 1 esp on
parted -s /dev/nvme0n1 unit MiB mkpart BOOT xfs 513 1025
parted -s /dev/nvme0n1 unit MiB mkpart ROOT xfs 1025 102400
parted -s /dev/nvme0n1 unit MiB mkpart DATA xfs 102400 100%
从 1 MiB 开始,天然对齐到 4k/8k 物理扇区。
print -l 可验证起始扇区是 2048 的倍数(512B 扇区时)。
3)文件系统与挂载
我习惯系统盘用 ext4(稳妥),数据盘用 XFS(大文件/并发更友好):
mkfs.vfat -F32 /dev/nvme0n1p1
mkfs.xfs -f /dev/nvme0n1p2 # /boot
mkfs.ext4 -F -E lazy_itable_init=0,lazy_journal_init=0 /dev/nvme0n1p3 # /
mkfs.xfs -f -m crc=1,finobt=1 -i size=512 -n size=4096 /dev/nvme0n1p4 # /data
挂载建议(/etc/fstab):
/dev/nvme0n1p3 / ext4 defaults,noatime,nodiratime 0 1
/dev/nvme0n1p2 /boot xfs defaults 0 2
/dev/nvme0n1p4 /data xfs defaults,noatime,inode64 0 2
不建议 discard 挂载选项,改为启用周期性 TRIM:
systemctl enable fstrim.timer
systemctl start fstrim.timer
4)I/O 调度器与读写前置
# NVMe 走多队列,推荐 'none'(旧内核可能显示 'noop')
cat /sys/block/nvme0n1/queue/scheduler
echo none > /sys/block/nvme0n1/queue/scheduler
# 合理的 readahead(示例 256 KiB)
blockdev --setra 512 /dev/nvme0n1
fio/pgbench 实测结果
注:每组测试跑 3 次,取中位数;温度稳定在 40–45℃;CPU 占用不成瓶颈。以下 IOPS/吞吐与延迟均为我机房现场数据,单位见表头。
1)随机 4k 读(QD32,4 job,IOPS)
| 布局 | IOPS(万) | P99 延迟(µs) |
|---|---|---|
| A:NVMe 系统 / SATA 数据 | 8.5 | 1450 |
| B:SATA 系统 / NVMe(PLP) 数据 | 64.0 | 480 |
| C:NVMe 分区(前100G系统/其余数据) | 63.2 | 495 |
| D:SATA 系统 / NVMe(无PLP) 数据 | 61.5 | 560 |
结论:随机读主要看介质;NVMe 放数据盘优势巨大。是否与系统共盘几乎不影响读性能(C≈B)。
2)随机 4k 写(QD32,4 job,IOPS;不强制fsync)
| 布局 | IOPS(万) | P99 延迟(µs) |
|---|---|---|
| A:NVMe 系统 / SATA 数据 | 4.1 | 6800 |
| B:SATA 系统 / NVMe(PLP) 数据 | 14.0 | 2700 |
| C:NVMe 分区(系统+数据) | 13.7 | 2800 |
| D:SATA 系统 / NVMe(无PLP) 数据 | 12.1 | 3300 |
结论:写入吞吐 NVMe 仍碾压 SATA;PLP 对非 fsync写入影响不大,但 NVMe 的稳定性更好。
3)4k 写 fsync=1(QD=1,单 job,ops/s)
| 布局 | ops/s(千次) | P99 延迟(ms) |
|---|---|---|
| A:NVMe 系统 / SATA 数据 | 2.3 | 13.0 |
| B:SATA 系统 / NVMe(PLP) 数据 | 18.0 | 1.7 |
| C:NVMe 分区(系统+数据) | 17.6 | 1.8 |
| D:SATA 系统 / NVMe(无PLP) 数据 | 5.8 | 5.6 |
结论(关键):带 PLP 的 NVMe 在 fsync 场景下领先 3~8 倍。无 PLP 的 NVMe 在强一致写入时性能显著掉队,且遇掉电风险时数据更不安全。
4)128k 顺序吞吐(GB/s)
| 布局 | 读(GB/s) | 写(GB/s) |
|---|---|---|
| A:NVMe 系统 / SATA 数据 | 0.54 | 0.50 |
| B:SATA 系统 / NVMe(PLP) 数据 | 3.50 | 2.80 |
| C:NVMe 分区(系统+数据) | 3.47 | 2.75 |
| D:SATA 系统 / NVMe(无PLP) 数据 | 3.45 | 2.60 |
结论:顺序吞吐 NVMe 明显占优;是否与系统共盘影响极小。
5)pgbench(TPS,-c 32 -j 16 -T 300)
| 布局 | TPS | P99 提交延迟(ms) |
|---|---|---|
| A:NVMe 系统 / SATA 数据 | 3,300 | 28.4 |
| B:SATA 系统 / NVMe(PLP) 数据 | 14,200 | 6.8 |
| C:NVMe 分区(系统+数据) | 13,900 | 7.1 |
| D:SATA 系统 / NVMe(无PLP) 数据 | 5,100 | 17.9 |
结论:只要把 数据与 WAL 放到带 PLP 的 NVMe 上,TPS 立刻提升 3–4 倍;无 PLP 的 NVMe 在强一致事务下也拉胯。
为什么 PLP 如此关键?
数据库/支付/订单类场景会频繁调用 fsync() 确保写入落盘。
带 PLP 的企业级 NVMe(电容阵列 + 固件保证)能在断电时把易失缓存中的数据安全转存到 NAND;在有序写回与 cache flush 上更激进且仍然安全,因此 fsync 延迟更低、抖动更小。
无 PLP 的 NVMe 为确保崩溃一致性,往往需要更保守的 flush 策略,性能显著下降;极端掉电还可能导致元数据损坏(哪怕你使用了 barrier/journal 机制,也只是把风险降低,而不是消灭)。
实操建议(可直接套用)
优先顺序
- 数据库/消息队列/高价值日志 → NVMe(企业级,带 PLP)
- 系统盘 → SATA SSD 或 NVMe 切 100 GiB 分区(方案 B 或 C)
- 预算有限只有 1 块 NVMe?方案 C:前 100 GiB 给系统,其余给数据,依然有 98% 的 NVMe 数据盘性能。
分区/对齐
- 使用 GPT,从 1 MiB 起创建分区;避开旧 MBR/CHS 的齐次误差。
- 验证起始扇区是否为 2048 的倍数。
文件系统
- 系统盘 ext4(稳妥),数据盘 xfs(并发友好);noatime + fstrim.timer。
- XFS 建议 -i size=512 -n size=4096,大目录与小文件场景兼顾。
调度器与内核
- NVMe 调度器 none,合理调大 readahead(例如 256 KiB)。
- tuned-adm profile throughput-performance,对批量 IO 友好。
电力与保护
- PLP 优先级=高;同时配套 UPS 与定期备份(快照 + 远端对象存储)。
- 生产库开启 synchronous_commit=on、full_page_writes=on;不要为 TPS 牺牲一致性。
监控与告警
- nvme smart-log 温度、介质错误、可用备用块;
- iostat -x 1 盯 util、await;
- 业务侧暴露 P95/P99 fsync 延迟与失败率。
复现实验:可复制粘贴的命令
fio(示例)
# 随机读
fio --name=randread --filename=/data/fio.test --size=100G \
--bs=4k --ioengine=libaio --iodepth=32 --rw=randread \
--direct=1 --numjobs=4 --time_based --runtime=60 --group_reporting
# 随机写(非 fsync)
fio --name=randwrite --filename=/data/fio.test --size=100G \
--bs=4k --ioengine=libaio --iodepth=32 --rw=randwrite \
--direct=1 --numjobs=4 --time_based --runtime=60 --group_reporting
# 4k 写 + fsync=1(数据库最敏感)
fio --name=fsyncwrite --filename=/data/fio.fsync --size=10G \
--bs=4k --ioengine=libaio --iodepth=1 --rw=write \
--fsync=1 --direct=1 --numjobs=1 --time_based --runtime=60 --group_reporting
# 128k 顺序
fio --name=seqread --filename=/data/fio.seq --size=100G \
--bs=128k --ioengine=libaio --iodepth=32 --rw=read \
--direct=1 --numjobs=4 --time_based --runtime=60 --group_reporting
PostgreSQL(pgbench)快速跑法
# 假设 /data 为目标盘(切换不同布局时仅变更此挂载点)
initdb -D /data/pgdata
echo "synchronous_commit=on" >> /data/pgdata/postgresql.conf
echo "full_page_writes=on" >> /data/pgdata/postgresql.conf
pg_ctl -D /data/pgdata -l /data/pgdata/logfile start
createdb bench
pgbench -i -s 100 bench
pgbench -c 32 -j 16 -T 300 bench
现场坑位与应急记要
坑1:无 PLP NVMe 在高峰 fsync 抖动严重
现象:P99 延迟从 2ms 飙到 20ms+;
处理:凌晨切业务只读,热迁移 WAL 到企业级 NVMe,TPS 与延迟恢复。
坑2:克隆系统后 XFS UUID 冲突
现象:/data 无法挂载;
处理:xfs_admin -U generate /dev/nvme0n1p4 重新生成 UUID,更新 /etc/fstab。
坑3:CentOS 7 旧内核某些机型上 NVMe 调度器显示异常
处理:确认 blk-mq 生效;不行就升级内核到 ELRepo kernel-lt(保守)后再测。
坑4:UEFI 启动项丢失(切分 NVMe 做系统盘时遇到)
处理:用 efibootmgr 重建启动项;必要时 chroot + grub2-install。
坑5:错误使用 discard 导致业务延迟抖动
处理:移除挂载 discard,改用 fstrim.timer 周期执行。
我给到的最终选择题答案
如果你跑的是数据库/支付/订单/队列这类强一致写入场景:
- 选 B 或 C:让 NVMe(带 PLP)做数据盘,SATA 做系统或 NVMe 切 100 GiB 给系统。
- 你的收益是:pgbench TPS 3–4 倍提升,P99 延迟大幅下降,故障边界也更可控。
如果你的业务主要是读多写少、无强一致:
- 也建议NVMe 放数据盘。系统盘放 NVMe 的意义很小(除非你密集做容器镜像构建/IO 很重的系统任务)。
只有一块 NVMe 的情况:
- 方案 C(前 100 GiB 系统,其余数据)几乎与纯数据 NVMe(方案 B)等效(本次测试差 <2%)。
NVMe 的价值 95% 在“数据盘”,而不是“系统盘”;PLP 是数据库/日志类场景的“硬性选项”。
我在香港机房把 WAL 切回企业级 NVMe,pgbench TPS 从 5k 爬回 14k,P99 落到 7ms。业务同事在群里丢了个“稳了”的表情包,我把凌晨的咖啡倒掉,关了告警声音。
NVMe 做系统盘还是数据盘? 这次不用再争了。把它用在最需要它的地方——数据与 WAL 上;对齐、文件系统、调度器一步不落;PLP 该上就上。下一次凌晨的短信,大概率会少很多