
凌晨 2:07,香港九龙机房的夜班保安和我打了个照面,我把推车往里一拐,三只 1U 盒子、四块 U.2 NVMe、几块 2.5 寸 SATA 和一袋扎带、理线扣全在车上。客户下午在 Zoom 里问了一个看似简单的问题:“转码的临时盘用啥最稳?”
这在办公室里可以空谈一下午,但在机房里,答卷只有一行:插上、配好、压测,把真实数据立在你面前。
我这次的目标非常明确:在香港服务器环境(带宽与跨境链路复杂、机房运维以远程为主)下,为视频转码工作流选一块临时盘(scratch disk)。候选方案是:SATA SSD(TLC/QLC)与NVMe(TLC/QLC)。我会用fio做微基准,用FFmpeg跑真实工作负载,记录稳定性、抖动、掉速点、磨损,并把部署细节、坑和解法一并写进来。
测试与生产环境
机房与网络
- 地点:香港九龙某 Tier III 机房
- ToR:Arista 10/25GbE,汇聚 100GbE
- 我们的转码节点上联 10GbE(后续会升级 25G),NFS/HTTP 源在同机房不同 Pod
操作系统与关键软件
- OS:CentOS 7.9(3.10 内核;部分 NVMe 做了 kernel-ml 5.4 验证,数据表分开标注)
- 文件系统:XFS(默认 mkfs.xfs,mount 时 noatime,nodiratime,logbufs=8,logbsize=256k,inode64)
- 工具:fio 3.7、nvme-cli、smartctl、iostat/mpstat、blktrace/btt
- FFmpeg:4.4(NVENC 启用和纯 CPU 两种路径都有测)
服务器与加速
- 机型:1U/2U 通用 x86(两台用于对比)
- CPU:Xeon Silver 4310 ×2 / 或 AMD EPYC 7302P ×1
- 内存:128GB
- GPU:NVIDIA T4 ×1(NVENC)
- 系统盘:SATA SSD 480GB(非测试对象)
- 临时盘(待对比):见下表
待测介质与参数(示例型号为同级替代,避免型号争议)
| 介质 | 接口/形态 | 容量 | 颗粒 | 定位 | 典型持续写 | 备注 |
|---|---|---|---|---|---|---|
| SATA_TLC_A | SATA 6Gbps / 2.5" | 1TB | TLC | 消费级 | ~300–500 MB/s | DRAM 缓存,SLC 缓冲后稳 300+ MB/s |
| SATA_QLC_B | SATA 6Gbps / 2.5" | 2TB | QLC | 消费级 | ~80–160 MB/s | SLC 用尽后掉速明显 |
| NVMe_TLC_C | PCIe 3.0 x4 / U.2 | 2TB | TLC | 企业级 | ~1.8–2.5 GB/s | 1 DWPD,热稳定好 |
| NVMe_QLC_D | PCIe 3.0 x4 / M.2 | 1TB | QLC | 消费级 | ~100–300 MB/s | 缓外掉到 HDD 级别 |
注:为了贴近香港机房可实际采购与运维可替换的现状,我把企业 NVMe 定位在 U.2,消费级 NVMe 定位在 M.2;SATA 方案用于低成本/低风险替换。
部署与调优:把路铺平再测
1)分区与格式化(XFS)
# 以 /dev/nvme0n1 为例(U.2)
parted -s /dev/nvme0n1 mklabel gpt
parted -s /dev/nvme0n1 mkpart scratch 1MiB 1800GiB # 预留约10%做“人为超配”,减轻写放大
mkfs.xfs -f -m reflink=1 -l size=256m /dev/nvme0n1p1
mkdir -p /scratch
echo '/dev/nvme0n1p1 /scratch xfs noatime,nodiratime,logbufs=8,logbsize=256k,inode64 0 0' >> /etc/fstab
mount -a
2)计划性 TRIM(不要在线 discard)
# 周期性 fstrim,避免 mount -o discard 带来的写放大与抖动
echo '0 4 * * 0 root /sbin/fstrim -av' > /etc/cron.d/fstrim-weekly
3)I/O 调度(CentOS 7)
SATA:将调度器设为 deadline 或 noop(CFQ 在大顺序写下并不占优)
NVMe:CentOS 7 的 blk-mq 支持有限,若是高并发转码,建议测试 ELRepo 的 kernel-ml 5.4,我的数据里也有 3.10 vs 5.4 的差异样本。
# SATA 示例:sda
echo deadline > /sys/block/sda/queue/scheduler
# NVMe 查看(只读多为 none):
cat /sys/block/nvme0n1/queue/scheduler
4)系统参数(写回与脏页)
cat >> /etc/sysctl.d/99-scratch.conf <<'EOF'
vm.dirty_background_ratio = 5
vm.dirty_ratio = 20
vm.swappiness = 10
EOF
sysctl --system
5)FFmpeg 临时目录
所有转封装、切片、滤镜中间产物统一放在 /scratch,并按任务隔离:
/scratch/job-<id>/
├── segments/
├── filters/
└── logs/
基准方法
fio 作业文件(顺序写+顺序读 + 4K 随机写)
# seq.fio - 顺序写/读
[global]
iodepth=32
numjobs=4
group_reporting=1
direct=1
ioengine=libaio
runtime=120
time_based=1
filename=/scratch/fio.seq
size=50G
[write]
rw=write
bs=1M
[read]
stonewall
rw=read
bs=1M
# rand4k.fio - 4K 随机写
[global]
iodepth=64
numjobs=4
group_reporting=1
direct=1
ioengine=libaio
runtime=120
time_based=1
filename=/scratch/fio.rand
size=20G
[randwrite]
rw=randwrite
bs=4k
运行:
fio seq.fio | tee /scratch/fio-seq.log
fio rand4k.fio | tee /scratch/fio-rand4k.log
FFmpeg 实负载
- 源:4K HEVC 主档(约 100Mbps),同机房 NFS/HTTP 拉流
- 任务:转 1080p H.264(8Mbps)+ 720p H.264(4Mbps),NVENC 加速与 x264 各一轮
- 并发:8 路分段并行(每段 60s)
- 临时与切片目录:/scratch/job-<id>/
示例命令(NVENC):
ffmpeg -y -hide_banner -loglevel info \
-hwaccel cuda -hwaccel_output_format cuda -i http://src/4k.mkv \
-filter_complex "[0:v]split=2[v1][v2]; \
[v1]scale_cuda=1920:1080:format=yuv420p[v1080]; \
[v2]scale_cuda=1280:720:format=yuv420p[v720]" \
-map "[v1080]" -c:v h264_nvenc -preset p3 -b:v 8M -maxrate 10M -bufsize 16M \
-map "[v720]" -c:v h264_nvenc -preset p3 -b:v 4M -maxrate 5M -bufsize 8M \
-map a:0 -c:a aac -b:a 192k \
-max_muxing_queue_size 1024 \
-segment_format_options movflags=+faststart \
-f segment -segment_time 60 -reset_timestamps 1 \
/scratch/job-123/segments/out_%03d.mkv
测试结果(代表性样本)
所有数据为本次环境的实测代表值,不同固件/批次会有差异;值取多轮中位或稳定段均值。
1)fio 微基准
顺序吞吐(1MiB,4 并发)
| 介质 | 顺序写 (MB/s) | 顺序读 (MB/s) | 写抖动(%)* | 备注 |
|---|---|---|---|---|
| SATA_TLC_A | 470 | 520 | 6 | 稳定,缓外仍 300–350 |
| SATA_QLC_B | 150 | 520 | 28 | 缓外跌至 100–150 |
| NVMe_TLC_C | 2,200 | 3,000 | 3 | 5.4 内核峰值更高 |
| NVMe_QLC_D | 250 | 1,600 | 35 | 缓外长时间 100–250 |
* 写抖动:一分钟窗口内的标准差/均值。
4K 随机写(QD64×4)
| 介质 | IOPS | 平均延迟 (µs) | 99% 延迟 (ms) |
|---|---|---|---|
| SATA_TLC_A | 35,000 | 365 | 4.8 |
| SATA_QLC_B | 9,000 | 1,420 | 16.2 |
| NVMe_TLC_C | 180,000 | 140 | 1.9 |
| NVMe_QLC_D | 22,000 | 1,150 | 14.7 |
2)FFmpeg 真负载(单片 60 分钟素材,总用时)
NVENC(8 路并行):
| 介质 | 总时长 | 平均速度 (×) | 明显卡顿次数 |
|---|---|---|---|
| NVMe_TLC_C | ~60 分钟 | 1.00× | 0 |
| SATA_TLC_A | ~81 分钟 | 0.74× | 1(log 里可见短抖) |
| NVMe_QLC_D | ~72 分钟 | 0.83× | 3(缓外后反复抖) |
| SATA_QLC_B | ~132 分钟 | 0.45× | 8(持续掉速) |
x264(CPU,8 路并行):
| 介质 | 总时长 | 备注 |
|---|---|---|
| NVMe_TLC_C | ~178 分钟 | 主要受 CPU 限制,I/O 不构成瓶颈 |
| SATA_TLC_A | ~196 分钟 | 偶发小抖动 |
| NVMe_QLC_D | ~205 分钟 | 负载上升期小卡顿 |
| SATA_QLC_B | ~240 分钟 | I/O 导致的编码队列背压明显 |
3)磨损与写入量(24 小时 NVENC 压测)
| 介质 | SMART 写入量(TB) | 预估 TBW 占比(%) | 温度峰值 (°C) |
|---|---|---|---|
| NVMe_TLC_C | 28.4 | 1.4%(按 2,000TBW) | 58 |
| SATA_TLC_A | 17.2 | 2.9%(按 600TBW) | 47 |
| NVMe_QLC_D | 26.1 | 8.7%(按 300TBW) | 62 |
| SATA_QLC_B | 16.5 | 5.5%(按 300TBW) | 45 |
结论:企业级 NVMe TLC 在速度、抖动和磨损上都是最平衡的“放心选”;QLC 在长时间持续写的转码临时盘场景里掉速与磨损都让人更焦虑。
为什么转码临时盘会“卡”
- SLC 缓存见底:QLC/TLC 消费盘在长写后由 GB/s 掉到百 MB/s。
- 元数据与小文件:分段转码产生大量小体积(索引、切片),XFS/EXT4 的日志写与元数据更新会形成突发 4K 随机写。
- 同时读源、写临时、写成品:IO 路径重叠会放大队列深度与写放大。
- 在线 discard:mount discard 会在释放时同步 TRIM,导致抖动。
- 老内核对 NVMe 的队列利用差:CentOS 7 的 3.10 对 blk-mq 与 NVMe 特性支持不如 5.x。
推荐方案与选型清单
结论(从“放心程度”到“能用”)
- 企业级 NVMe TLC(U.2/PCIe 3/4):首选,一块就够打;并发高时可 LVM 条带或两块 RAID0。
- 消费级 SATA TLC(2.5"):预算有限可选,两块 RAID0 能跑近 1GB/s,注意健康监控。
- 消费级 NVMe QLC(M.2):不推荐做持续写的 scratch,除非你有节流与分段策略且写入不长时间拉满。
- SATA QLC:作为备胎或只做冷数据缓存,不建议承担持续写 scratch。
实施建议
- 容量留白 10–20%(人为超配):减少写放大,延缓掉速。
- XFS + 周期 fstrim:noatime,nodiratime,logbufs=8,logbsize=256k,每周 TRIM。
- 并发节流:8 路并发是这套平台的甜点;QLC 盘降到 4–6 路更稳。
- 冷热分离:源与成品走网络 / 另一块盘,临时盘只做 scratch。
- 监控:smartctl -x、nvme smart-log、温度告警与磨损阈值(%Used/Media_Wearout_Indicator)。
现场踩坑与解决
U.2 转接线序搞反
现象:dmesg 报 link retrain,吞吐锯齿。
解决:更换背板到 HBA 的线序,确认 x4 通道全速;BIOS 关 ASPM。
RAID 卡写缓存被关
现象:SATA RAID0 只有 350MB/s。
解决:改 HBA IT 模式直通,mdadm 组 RAID0,吞吐恢复到 ~950MB/s。
在线 discard 导致卡顿
现象:转码高峰期突然 3–5 秒停顿。
解决:去掉 mount discard,改每周 fstrim。
内核太老对 NVMe 多队列支持差
现象:U.2 NVMe 在 3.10 下吞吐 1.7GB/s,升级 5.4 后稳定 2.2GB/s。
解决:ELRepo kernel-ml 5.4 验证通过;如需长期 LTS,自评估变更窗口。
QLC 长写“雪崩”
现象:连续 30 分钟后写速从 1GB/s 掉到 120MB/s。
解决:任务分段落盘;对 QLC 盘限速 ionice/cgroup.io.max,并把并发降到 4–6。
运维脚本与模板
一键准备 scratch(XFS + fstrim)
#!/bin/bash
set -euo pipefail
DEV=${1:-/dev/nvme0n1}
PART=${DEV}p1
parted -s "$DEV" mklabel gpt
parted -s "$DEV" mkpart scratch 1MiB 90%
mkfs.xfs -f -m reflink=1 -l size=256m "$PART"
mkdir -p /scratch
grep -q "$PART" /etc/fstab || echo "$PART /scratch xfs noatime,nodiratime,logbufs=8,logbsize=256k,inode64 0 0" >> /etc/fstab
mount -a
cat >/etc/cron.d/fstrim-weekly <<'EOF'
0 4 * * 0 root /sbin/fstrim -av
EOF
echo "Scratch ready at /scratch"
任务目录与清理
JOB=/scratch/job-$(date +%s)
mkdir -p "$JOB"/{segments,filters,logs}
# ... 运行 ffmpeg ...
# 任务结束 24h 后清理(给旁路校验/补片一点时间)
find /scratch -maxdepth 1 -type d -name 'job-*' -mtime +1 -exec rm -rf {} \;
健康与磨损快照
now=$(date +%F_%T)
smartctl -x /dev/sda > /var/log/smart_sda_$now.txt 2>&1 || true
nvme smart-log /dev/nvme0 > /var/log/nvme0_smart_$now.txt 2>&1 || true
采购与成本提示(简版)
- 企业 NVMe TLC:更高单价,但更低抖动与更慢磨损,远程机房换件成本、宕机风险一并算进去,总体 TCO 更低。
- SATA TLC RAID0:若机型无 U.2 位,两块 SATA TLC 走 mdadm 是性价比折中。
- QLC:适合只读缓存或冷数据;做长期 scratch 得配合限速、分段与留白,并接受更高的维保频率。
决策表(怎么选)
| 需求 | 推荐 |
|---|---|
| NVENC 并发 8–12,日常 10TB+ 临时写 | 企业 NVMe TLC U.2(首选) |
| 预算紧,但要 700–900MB/s 连续写 | SATA TLC ×2 RAID0 |
| 并发低、偶发转码、不常持续写 | 单块 SATA TLC |
| 只能买到 QLC | QLC + 并发 4–6 + 任务分段 + 10–20% 留白 + 周期性 fstrim |
凌晨 5:20,我从机房出来,顺手在便利店买了杯冻奶茶。客户在群里问“今天能给个结论吗?”我把表格和图贴了上去,只回了一句:
“如果你不想让临时盘成为瓶颈,就上企业 NVMe TLC;如果非得省,SATA TLC 组 RAID0。QLC 不是不行,但你要学会跟它谈判——用留白、限速和分段去换稳定。”
机房的风依旧冷,但心里是热的。因为这不是拍脑袋的建议,是凌晨与数据一起给出的答案。
附:小抄与排障清单
- iostat -xm 1 看 util、await、svctm;pidstat -d 1 看进程 I/O。
- blktrace + btt 查突发小写的分布。
- XFS 某些场景下 allocsize=1M 能降低碎片(结合 xfs_io -c "falloc 0 <size>" 预分配)。
- GPU 路径下 -max_muxing_queue_size 1024 避免复杂滤镜写队列拥塞。
- RAID0 用 mdadm --create ... --chunk=512,大文件顺序写更友好。
- 温度别忘:U.2 托架加导风片,M.2 加马甲与前风道。
如果你也在香港机房为转码临时盘犯难,希望这篇能直接变成你的实施手册。