用NVME提高群晖NAS的性能

群晖的DSM有一个比较鸡肋的功能:SSD Cache,原因如下:

  1. 单条NVME只能做只读Cache,功能有限
  2. 一个SSD Cache只能加速一个存储池(DSM 6.2中的词汇,以前叫存储空间)
  3. 系统分区不在任何存储池里,所以系统分区上的东西都不能加速
  4. NVME只能做Cache,不能用来存数据

DMS的SSD Cache功能是基于Linux的dm-cache来实现的,理论上通过Hack是可以解决上面说的这些问题的,不过得摸透了DSM相关的所有逻辑才能搞,而且对DSM的侵入比较大。所以,有一个想法,直接解决第4条问题,想办法把NVME当成正常的磁盘来存数据。然后就可以按自己的想法,灵活地挑一些需要高速随机IO的数据放在NVME上,其它的数据还是放在磁盘上。

如果问群晖的技术支持可不可以这样做,回答是否定的——非常可以理解,在群晖的设备上,盘位就是钱,白给你增加两个盘位?想都别想。

研究了一番,找到了解决这个问题的办法,虽然不完美,但是可用,并且效果不错。以下内容基于DS918+的硬件,DSM 6.2,系统中所有的磁盘都是Basic模式。由于条件所限,我没有测试RAID或SHR的情况,但原理应该都是一样的,可以参考一下我的思路。DSM的RAID就是标准的Linux RAID,而SHR则是基于LVM搞出来的。

以下步骤请充分理解后自行操作,所有给出的命令仅供参考,切勿依样画葫芦。这里介绍的方法有可能会造成不可逆的数据损失,请自己评估风险。

1. 分区

安装NVME后,系统中会出现/dev/nvme0n*设备,并且在存储管理器中可以看到对应设备。不要在DSM中创建SSD Cache,因为我们准备自己管理这个硬件。

Basic模式下,DSM会把系统中的每块硬盘都分成三个区:

Device      Start         End     Sectors Size Type
/dev/sdd1    2048     4982527     4980480 2.4G Linux RAID
/dev/sdd2 4982528     9176831     4194304   2G Linux RAID
/dev/sdd3 9437184 11720840351 11711403168 5.5T Linux RAID

第一个分区用于存放DSM系统,第二个分区是SWAP,第三个分区是用户可用的存储空间。每块磁盘的前两个分区,分别组成/dev/md0和/dev/md1两个RAID设备,都是RAID 1的模式。

所以,我们也依样画葫芦,把NVME也用相同的方式分区(可以使用系统自带的parted工具),分出与别的磁盘大小一样的前两个分区,类型都设置为RAID。然后剩余的空间分为普通Linux分区,并格式化为你需要的文件系统(ext4或btrfs)。当然,如果你有多块NVME或者你愿意,你也可以把剩余空间分为RAID类型,创建md设备后再格式化。

2. 创建RAID组

分完区后,扩展md0和md1,把NVME的前两个分区,加到这两个RAID组中。

# 如果是2盘位的设备,大概只需要扩展成3就可以了 
$ sudo mdadm --grow --raid-devices=5 /dev/md0 
$ sudo mdadm --grow --raid-devices=5 /dev/md1
$ sudo mdadm --manage /dev/md0 --add /dev/nvme0n1p1
$ sudo mdadm --manage /dev/md1 --add /dev/nvme0n1p2

然后,把第三个数据分区mount到一个你喜欢的位置,比如:

sudo mount /dev/nvme0n1p3 /volume1/homes/admin/nvme

考虑到NVME和磁盘混合组成RAID,可以把所有磁盘(不包括NVME)的对应分区设为writemostly,这样可以让读操作尽量走NVME,提高性能:

sudo echo writemostly | tee /sys/block/md0/md/dev-sdX1/state
sudo echo writemostly | tee /sys/block/md1/md/dev-sdX2/state

这样做完以后,系统分区和SWAP分区都已经可以被NVME加速了。这一步是完全没有风险的,因为即使未来NVME从RAID 1组中意外丢失或损坏,也不会造成任何数据问题。

同时在DSM中,正常访问home/nvme就是访问NVME上的内容,这个目录可以当作普通的存储空间来使用,存放一些需要高速随机IO的数据。

3. 迁移数据

为了达到更好的性能,我们还应该把一些常用的对IO性能敏感的东西迁移到NVME上,包括但不限于:

  1. 系统的PostgreSQL/MariaDB数据库,通常位于/volume1/@database,但也可能分布在多块盘上
  2. 软件包,通常位于/volume1/@appstore,但也可能分布在多块盘上
  3. CloudSync的SQLite数据库,通常位于/volume1/@cloudsync
  4. Docker的镜像和容量数据,通常位于/volume1/@docker
  5. VMM的虚拟磁盘文件,通常位于/volume1/@Repository

迁移的方法很简单,先rsync,然后再mount –bind,比如:

sudo rsync -a /volume1/\@appstore/ /volume1/homes/admin/nvme/\@appstore/
sudo mount --bind /volume1/homes/admin/nvme/\@appstore /volume1/\@appstore

理想情况下,应该先把相关服务停止后(用synoservice命令可以启停服务)再迁移数据,避免迁移过程中有新数据写入造成不一致。为了保证万无一失,建议可以写个迁移脚本,在系统启动过程中所有服务还没有启动前运行一下这个脚本。

这一步是有风险的,因为万一未来某一次mount –bind没有成功或者没有做mount –bind,系统就无法访问到正确的数据了,这对于系统数据库之类的,还是有一定影响的,会造成数据不一致。

4. 重启自动生效

写一个脚本,用于在未来重启时重建RAID和mount相关目录,比如:

# 先把NVME上的数据盘mount起来
mount /dev/nvme0n1p3 /volume1/homes/admin/nvme
# 把SWAP的RAID 1扩容,并把NVME上的分区加进去
# 系统分区不需要,因为会自动完成。
# 但SWAP的RAID每次重启都会重建,所以每次都需要扩容。
mdadm --grow --raid-devices=5 /dev/md1
mdadm --manage /dev/md1 --add /dev/nvme0n1p2
# 把相关的目录mount --bind上去
mount --bind /volume4/homes/admin/nvme/\@appstore /volume4/\@appstore
mount --bind /volume4/homes/admin/nvme/\@cloudsync /volume4/\@cloudsync
mount --bind /volume4/homes/admin/nvme/\@database /volume4/\@database
mount --bind /volume4/homes/admin/nvme/\@docker /volume4/\@docker
mount --bind /volume4/homes/admin/nvme/\@Repository /volume4/\@Repository
# 重新设置RAID writemostly策略
echo writemostly > /sys/block/md0/md/dev-sda1/state
echo writemostly > /sys/block/md0/md/dev-sdb1/state
echo writemostly > /sys/block/md0/md/dev-sdc1/state
echo writemostly > /sys/block/md1/md/dev-sda2/state
echo writemostly > /sys/block/md1/md/dev-sdb2/state
echo writemostly > /sys/block/md1/md/dev-sdc2/state

修改/etc/rc,在SYNOINSTActionPostVolume这一行后面,增加一行对上述脚本的调用。SYNOINSTActionPostVolume执行完后,刚好是所有磁盘都mount好但是没有任何服务启动的时刻,所以这时做mount –bind是最合适的。如果你第三步数据迁移想在重启时做,也是加在这个位置。

这一步是比较不完美的,因为需要修改系统文件,而系统文件有可能会在更新DSM时被覆盖回去,万一被覆盖回去,系统启动后就是一个没有mount –bind的状态了,即使那时再改一遍脚本再重启,DB的一致性可能已经无法保证了。我暂时选用了一个带有一点防御性的做法(同样不是万无一失的):在更新DSM前,把/usr/sbin/reboot改名,这样更新完DSM后系统不会被自动重启,我就可以有机会检查/etc/rc有没有覆盖,如果被覆盖,可以自己改回来以后再重启系统。

5. 其它经验和坑

  • 数据迁移必须用mount –bind,不能用软链,已知有些应用组件在软链的情况下不能正常工作
  • /volume1/@tmp不能迁移到NVME,即使用mount –bind,也会造成Drive等组件不能正常工作
  • 现在的做法,NVME的第二个分区是后加到SWAP分区的RAID里的,所以每次重启都会有个重新同步的过程,IO会打高一会儿,并且完成后DSM会弹一个提示:一致性检查完成。但总体是可以忍的,所以我就没有去深究用什么方法可以在建立SWAP的RAID时就直接把它一起建进去了,因为我还是想尽可以少侵入DSM系统
  • 用户的共享文件夹也可以通过mount –bind迁到NVME上,但是这样做会造成在共享文件夹管理界面上不能正常查看共享文件夹相关信息,所以建议只对共享文件夹里的目录进行mount –bind
  • 如果你只有一条NVME,或者有两条但没有做RAID,那么迁移上去的那些系统文件都是单点(虽然原本在磁盘没有RAID的话是单点),需要留意其中的风险