What’s SSD TRIM and why use it?
最近我们的一个 Mongo cluster 总是在周末的时候有大量的 IO 操作,并严重影响了 Mongo 的性能。然后发现问题出现的时候总是会执行一个 cron job – fstrim:
$ cat /etc/crontab | grep weekly
47 6 * * 7 root test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.weekly )
$ cat /etc/cron.weekly/fstrim
#!/bin/sh
# trim all mounted file systems which support it
/sbin/fstrim --all || true
这是在 Ubuntu 16.04 上默认开启的(注意在 18.04 上变成了 systemd 来驱动,而不是 crontab),从 man 可以看到 fstrim 是用来清除文件系统上,特别是 SSD 不用的 block 的,并可以延长 SSD 的寿命。
通过 iostat 可以看到,fstrim 运行的时候,Mongo 的 IO write 数量增长了几千倍:
$ sudo iostat 3
Linux 4.4.0-1105-aws (mongo-03) 04/13/2020 _x86_64_ (64 CPU)
avg-cpu: %user %nice %system %iowait %steal %idle
3.27 0.00 0.58 0.18 0.00 95.97
Device: tps kB_read/s kB_wrtn/s kB_read kB_wrtn
loop0 0.00 0.00 0.00 0 0
xvda 0.00 0.00 0.00 0 0
xvdd 0.00 0.00 0.00 0 0
xvdb 0.00 0.00 0.00 0 0
nvme1n1 231.67 2488.00 560.00 7464 1680
nvme0n1 245.33 2478.67 561.33 7436 1684
nvme2n1 241.33 2634.67 637.33 7904 1912
nvme5n1 243.67 2472.00 617.33 7416 1852
nvme3n1 238.33 2493.33 525.33 7480 1576
nvme4n1 235.67 2457.33 529.33 7372 1588
nvme6n1 227.67 2248.00 640.00 6744 1920
nvme7n1 228.67 2198.67 557.33 6596 1672
md0 1931.33 19478.67 4628.00 58436 13884
avg-cpu: %user %nice %system %iowait %steal %idle
3.39 0.00 0.49 0.14 0.00 95.98
Device: tps kB_read/s kB_wrtn/s kB_read kB_wrtn
loop0 0.00 0.00 0.00 0 0
xvda 0.33 0.00 2.67 0 8
xvdd 0.00 0.00 0.00 0 0
xvdb 0.00 0.00 0.00 0 0
nvme1n1 251.33 2685.33 544.00 8056 1632
nvme0n1 256.67 2518.67 715.00 7556 2145
nvme2n1 243.33 2600.00 548.00 7800 1644
nvme5n1 256.00 2454.67 570.83 7364 1712
nvme3n1 238.00 2630.67 544.00 7892 1632
nvme4n1 218.33 2296.00 545.33 6888 1636
nvme6n1 256.67 2670.67 640.33 8012 1921
nvme7n1 270.33 2545.33 732.00 7636 2196
md0 2027.67 20408.00 4839.50 61224 14518
# after fstrim is called
avg-cpu: %user %nice %system %iowait %steal %idle
1.63 0.00 1.34 9.80 0.00 87.23
Device: tps kB_read/s kB_wrtn/s kB_read kB_wrtn
loop0 0.00 0.00 0.00 0 0
xvda 2.67 0.00 17.33 0 52
xvdd 0.00 0.00 0.00 0 0
xvdb 3.33 0.00 369.67 0 1109
nvme1n1 910.67 360.00 7540296.00 1080 22620888
nvme0n1 887.67 330.67 7400742.67 992 22202228
nvme2n1 892.33 336.00 7392044.00 1008 22176132
nvme5n1 890.00 293.33 7397830.67 880 22193492
nvme3n1 932.33 377.33 7653155.50 1132 22959466
nvme4n1 956.00 352.00 7917478.67 1056 23752436
nvme6n1 889.67 298.67 7421049.33 896 22263148
nvme7n1 892.67 293.33 7395156.00 880 22185468
md0 157869.00 2641.33 80663394.50 7924 241990183
avg-cpu: %user %nice %system %iowait %steal %idle
0.49 0.00 0.63 4.84 0.00 94.04
Device: tps kB_read/s kB_wrtn/s kB_read kB_wrtn
loop0 0.00 0.00 0.00 0 0
xvda 0.33 1.33 0.00 4 0
xvdd 0.00 0.00 0.00 0 0
xvdb 0.00 0.00 0.00 0 0
nvme1n1 997.00 17.33 8663381.33 52 25990144
nvme0n1 998.33 0.00 8689493.33 0 26068480
nvme2n1 1000.00 2.67 8686761.33 8 26060284
nvme5n1 999.67 6.67 8675158.67 20 26025476
nvme3n1 999.33 4.00 8678128.50 12 26034385
nvme4n1 923.00 8.00 8007872.00 24 24023616
nvme6n1 999.67 6.67 8683810.67 20 26051432
nvme7n1 997.67 5.33 8680789.33 16 26042368
md0 78771.67 96.00 40321945.83 288 120965837
另外,还发现我们挂载磁盘的时候,已经通过 discard
启用了 TRIM:
$ findmnt -O discard
TARGET SOURCE FSTYPE OPTIONS
/ /dev/xvda1 ext4 rw,relatime,discard,data=ordered
└─/mnt/data /dev/md0 xfs rw,noatime,attr2,discard,nobarrier,inode64,sunit=1024,swidth=8192,noquota
磁盘的 discard
option 叫做 “Continuous TRIM” 或者 “Online discard",而 fstrim 也叫做 "Periodic TRIM” 或 “Batch discard"。
从 https://wiki.archlinux.org/index.php/Solid_state_drive and https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/6/html/storage_administration_guide/ch02s04 看来,应该使用 TRIM,并且 periodic TRIM 更好。
fstrim 的 mannul 也说
Running fstrim frequently, or even using mount -o discard, might negatively affect the lifetime of poor-quality SSD devices. For most desktop and server systems the sufficient trimming frequency is once a week.
本质上这两种 TRIM 做的事情都一样,只是频次不太一样。而因为 TRIM 有不小的副作用,所以运行过于频繁,副作用也更大。
那 TRIM 是什么呢?又是为什么会影响 SSD 寿命呢?
How SSD write works #
SSD 在写数据的时候和 HHD 不同的一点是,SSD 不能直接重写一块区域的数据,而是需要先 erase,才能重新写。读写是按 page,比如 4KB,而 erase 是以 block 为单位,一个 block 一般有上百个 page,并且一块 SSD 只能够被擦除有限次数(比如10000次)。SSD 在重写数据的时候,一般会写到新的 page,在删除数据的时候,只是在 OS 做了标记,SSD 并不会实际删除数据(HHD 这点其实也是一样),所以当 SSD 空间不够的时候,OS 就可能会需要重写在已经被删除的地方,所以就需要 erase。而 erase 可能是一个比较“重”的操作,会影响性能。所以随着 SSD 空闲的空间越来越少,性能就会越差。
举个例子来说明一下 SSD 写数据和擦除的过程。我们假设一个 page 是 4KB,一个 block 是5个 page,并且当前 SSD 只剩下1个 block。然后先写入了一个 4K(1 page)的文件 A,再写入了一个8K(2 page)的文件B,随后又删除了文件A,注意这时 SSD 并没有把第一个 page 删除。最后我们写了一个 12KB(3 page) 的文件C,OS 因为知道第一个 page 已经被删除,所以会告诉 SSD 来擦除这个 page。SSD 因为要按 block 来擦除,所以要先把当前的数据放到 cache 中,然后删掉第一个 page,再把文件 C 写到空余的3个 page 中。所以为了写文件C 的12KB,我们需要先读12KB,再写20KB,由于产生了写放大,所以比平时慢不少。
所以如果我们有更多的空间,就能避免擦除,来提高写性能。这也是很多 SSD 一开始做格式化的时候,会留一部分空间不用。
TRIM #
OS 可以通过 TRIM 命令来告诉 SSD 哪些数据已经被删除了,这样 SSD 就可以在 GC 时做优化,从而减少擦除的次数,并且减少在写数据时的擦除,从而提高写性能。
可以看到即使是 TRIM 也不能完全消除 SSD 擦除的操作,只是有所改善。
而 batch TRIM 更好的原因是在 TRIM 的早期实现中是 non-queued 的,会等当前操作完全,然后触发 TRIM,再恢复正常响应,所以性能比较差。所以在低峰时来做 batch TRIM,可以减少对系统的影响。SATA 3.1 后已经改成了 ququed trim,从而可以减少对性能的影响。
但从各方面的资料看来,比如 fstrim 的 manual,以及 https://wiki.archlinux.org/index.php/Solid_state_drive/NVMe#Discards 里提到 Intel 对于 discard 的建议(原因是 Linux Kernel 的 TRIM 实现比较慢),在低峰的时候,周期性地用 batch trim 似乎都是更好的选择,特别是在你不清楚你的设备时候支持 queued trim 的情况下。
当然,如果 fstrim 对性能影响也比较大,比如你的应用没有一个低峰的时刻可以用来跑 fstrim,那可以先调整 fstrim 的参数,来减少影响。实在不行,可以在测试没问题的前提下使用 Online discard,但千万不要同时使用。