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,但千万不要同时使用。

 
9
Kudos
 
9
Kudos

Now read this

ActiveRecord 和 Ecto 的比较

ActiveRecord 是 Ruby on Rails 的 Model 层,是一个 ORM(Object-relational mapping)。Ecto 是 Elixir 实现的一个库,类似于 ORM。不管是不是 ORM,二者本质上都是在各自的语言层面,对于数据库操作提供了抽象,能让我们更方便地和数据库交互,而不是直接通过 SQL 的方式,并且对表中的数据做了映射,从而方便进行后续逻辑的处理。 这篇文章并不打算来争个孰优孰劣,很多时候对比的作用更是加深对于事物的认识... Continue →