rsyncを高速に

Azure

はじめに:rsyncに限らず、高速にAzureにデータをコピーできる他の手段でも良いなら、azcopyを使おう。

1TBのデータをrsyncでインターネット経由でAzureに同期したいわけですが、普通に

$ rsync source_directory/ rsync://myrsyncserver.japaneast.cloudapp.azure.com/target_directory/

なんてやってたら、超絶時間がかかるわけで、何とかしたい。

拙宅ではauひかりホーム10Gを導入しているので、Azure東日本リージョンまでそこそこのスピードが出るはず。
Standard_D16s_v3のVMにUbuntu 18.04-LTS、OS DiskはIOPSをある程度確保するために512GBのPremium_SSD、Data DiskはPremium SSDではIOPSの限界である7,500を出せる2TB(P40)を2本付けてRAID0に。Read/Writeキャッシュ共に設定しておくこと。Azure Marketplaceからデプロイする場合、Azure用カーネルを採用しているディストリビューションを使った方が性能が良いので、今回はUbuntu 18とした。

$ sudo fdisk /dev/sdc(d)

して、Linux RAID用にタイプfdを設定したパーティションを作成後、

$ sudo mdadm --create /dev/md0 --level=raid0 --raid-devices=2 /dev/sdc1 /dev/sdd1
$ sudo mkfs.xfs /dev/md0
$ sudo blkid /dev/md0

で得られたUUIDを/etc/fstabに追加しておく。

UUID=3149b0c8-fdb1-439f-a5a2-a14771077c24	/opt/ext	xfs	defaults,discard	0 0

で、マウント出来るかチェック。

$ sudo mkdir /opt/ext
$ sudo mount /opt/ext

fioをインストールして簡易チェック。

$ sudo apt-get install fio
$ fio -filename=/opt/ext/test2g -direct=1 -rw=write -bs=4k -size=2G -numjobs=64 -runtime=10 -group_reporting -name=file1 | grep "write:"
  write: IOPS=32.8k, BW=128MiB/s (134MB/s)(1281MiB/10002msec)
$ fio -filename=/opt/ext/test2g -direct=1 -rw=randwrite -bs=4k -size=2G -numjobs=64 -runtime=10 -group_reporting -name=file1 | grep "write:"
  write: IOPS=32.7k, BW=128MiB/s (134MB/s)(1280MiB/10020msec)
$ fio -filename=/opt/ext/test2g -direct=1 -rw=write -bs=32m -size=2G -numjobs=64 -runtime=10 -group_reporting -name=file1 | grep "write:"
  write: IOPS=8, BW=256MiB/s (269MB/s)(4384MiB/17094msec)

まあ、そこそこ出てます。拙宅のサーバのデータ転送元となるNVMe SSDだと、

$ fio -filename=./test2g -direct=1 -rw=write -bs=4k -size=2G -numjobs=64 -runtime=10 -group_reporting -name=file1 | grep "write:"
  write: IOPS=101k, BW=395MiB/s (414MB/s)(3951MiB/10002msec)
$ fio -filename=./test2g -direct=1 -rw=randwrite -bs=4k -size=2G -numjobs=64 -runtime=10 -group_reporting -name=file1 | grep "write:"
  write: IOPS=248k, BW=967MiB/s (1014MB/s)(9673MiB/10002msec)
$ fio -filename=./test2g -direct=1 -rw=write -bs=32m -size=2G -numjobs=64 -runtime=10 -group_reporting -name=file1 | grep "write:"
  write: IOPS=38, BW=1236MiB/s (1296MB/s)(13.9GiB/11517msec)

こんな感じだったりするわけだけど。

Standard_D16s_v3はAccelerated Networkingに対応してるので、念の為、IPv4関連のカーネルパラメータを設定。/etc/sysctl.confに下記パラメータを追加。

net.ipv4.tcp_timestamps=0
net.ipv4.tcp_sack=0
net.ipv4.tcp_low_latency=1
net.ipv4.tcp_window_scaling=0
net.ipv4.tcp_dsack=0
net.ipv4.tcp_tw_reuse=1
net.core.netdev_max_backlog=250000
net.core.rmem_max=16777216
net.core.wmem_max=16777216
net.core.rmem_default=16777216
net.core.wmem_default=16777216
net.core.optmem_max=16777216
net.ipv4.tcp_rmem=4096 87380 16777216
net.ipv4.tcp_wmem=4096 65536 16777216

で、適用してみたんだけど、特に性能に変化は見られなかったので、必要ないかも知れない。備忘録として残しておく。

$ sudo sysctl -p /etc/sysctl.conf

iperf3をインストールして、Network Security Groupで5201/tcpを開ける。

$ sudo apt-get install iperf3

これでベンチの準備おけ。

$ iperf3 -s

で5201/tcpをリッスン。

クライアント側でiperf3を実行してみる。クライアント側の性能の方がサーバ側より低ければ、先にサチるのはクライアント側となる。拙宅のサーバの方が非力なので、どこでサチるか探る。

bw=8; while [ $bw -le 100 ]; do iperf3 -c myrsyncserver.japaneast.cloudapp.azure.com -l $bw -b 200M | grep 'sender'; bw=$(( $bw + 8 )); done
[  4]   0.00-10.00  sec  35.9 MBytes  30.1 Mbits/sec    5             sender (8)
[  4]   0.00-10.00  sec  70.7 MBytes  59.3 Mbits/sec    9             sender (16)
[  4]   0.00-10.00  sec   105 MBytes  87.9 Mbits/sec   11             sender (24)
[  4]   0.00-10.00  sec   140 MBytes   117 Mbits/sec   12             sender (32) 
[  4]   0.00-10.00  sec   134 MBytes   113 Mbits/sec   25             sender (40)
[  4]   0.00-10.00  sec   166 MBytes   139 Mbits/sec   14             sender (48)
[  4]   0.00-10.00  sec   146 MBytes   123 Mbits/sec   13             sender (56)
[  4]   0.00-10.00  sec   165 MBytes   138 Mbits/sec   14             sender (64)
[  4]   0.00-10.00  sec   167 MBytes   140 Mbits/sec   12             sender (72)
[  4]   0.00-10.00  sec   145 MBytes   122 Mbits/sec   10             sender (80)
[  4]   0.00-10.00  sec   155 MBytes   130 Mbits/sec   13             sender (88)
[  4]   0.00-10.00  sec   165 MBytes   138 Mbits/sec   12             sender (96)

40コネクションあたりからサチってるので、その周辺だけ再度測定。

(修正)ここで変化させていたのは”-l”ですので、”length of buffer to read or write”でした。指摘をいただいた、Fumiyasu Satohさん、ありがとうございます!

bw=36; while [ $bw -le 48 ]; do iperf3 -c myrsyncserver.japaneast.cloudapp.azure.com -l $bw -b 200M | grep 'sender'; bw=$(( $bw + 2 )); done
[  4]   0.00-10.00  sec   136 MBytes   114 Mbits/sec    6             sender (36)
[  4]   0.00-10.00  sec   161 MBytes   135 Mbits/sec    3             sender (38)
[  4]   0.00-10.00  sec   138 MBytes   116 Mbits/sec    4             sender (40)
[  4]   0.00-10.00  sec   169 MBytes   142 Mbits/sec    3             sender (42)
[  4]   0.00-10.00  sec   147 MBytes   123 Mbits/sec    2             sender (44)
[  4]   0.00-10.00  sec   173 MBytes   145 Mbits/sec    0             sender (46)
[  4]   0.00-10.00  sec   136 MBytes   114 Mbits/sec    0             sender (48)

バラついたので、もう一回。

bw=36; while [ $bw -le 48 ]; do iperf3 -c myrsyncserver.japaneast.cloudapp.azure.com -l $bw -b 200M | grep 'sender'; bw=$(( $bw + 2 )); done
[  4]   0.00-10.00  sec   157 MBytes   132 Mbits/sec   12             sender (36)
[  4]   0.00-10.00  sec   159 MBytes   134 Mbits/sec    5             sender (38)
[  4]   0.00-10.00  sec   168 MBytes   141 Mbits/sec    3             sender (40)
[  4]   0.00-10.00  sec   171 MBytes   143 Mbits/sec    1             sender (42)
[  4]   0.00-10.00  sec   165 MBytes   139 Mbits/sec    2             sender (44)
[  4]   0.00-10.00  sec   160 MBytes   134 Mbits/sec    3             sender (46)
[  4]   0.00-10.00  sec   158 MBytes   132 Mbits/sec    1             sender

40 or 42コネクションが最適っぽいことが分かる。

(修正)iperfの”-P”オプションを変化させながら、最適なコネクション数を探ってみる。

con=8; while [ $con -le 100 ]; do iperf3 -c myrsyncserver.japaneast.cloudapp.azure.com -P $con -b 200M | grep 'SUM' | grep 'sender'; con=$(( $con + 8 )); done
[SUM]   0.00-10.00  sec  1.23 GBytes  1.06 Gbits/sec  112             sender (8)
[SUM]   0.00-10.00  sec  2.58 GBytes  2.22 Gbits/sec  244             sender (16)
[SUM]   0.00-10.00  sec  4.09 GBytes  3.51 Gbits/sec  1564             sender (24)
[SUM]   0.00-10.00  sec  5.23 GBytes  4.49 Gbits/sec  3598             sender (32)
[SUM]   0.00-10.00  sec  6.79 GBytes  5.84 Gbits/sec  3178             sender (40)
[SUM]   0.00-10.00  sec  7.34 GBytes  6.31 Gbits/sec  6773             sender (48)
[SUM]   0.00-10.00  sec  7.34 GBytes  6.31 Gbits/sec  16697             sender (56)
[SUM]   0.00-10.00  sec  7.50 GBytes  6.44 Gbits/sec  22062             sender (64)
[SUM]   0.00-10.00  sec  7.49 GBytes  6.43 Gbits/sec  28432             sender (72)
[SUM]   0.00-10.00  sec  7.66 GBytes  6.58 Gbits/sec  35196             sender (80)
[SUM]   0.00-10.00  sec  7.66 GBytes  6.58 Gbits/sec  43696             sender (88)
[SUM]   0.00-10.00  sec  7.65 GBytes  6.57 Gbits/sec  47673             sender (96)

48コネクションあたりでサチっていて、6.6Gbpsあたりが限界っぽい結果。

さて、rsyncサーバを立てる。コネクションは40 or 42 48まではイケるっぽいのでその上のキリが良い数字で64にして、/etc/rsyncd.confを以下のような感じで書いて、

#
# Global options
#

#
# Module options
#
max connections = 64
charset = utf-8
[Data]
  path = /opt/ext/Data
  comment =
  use chroot = true
  uid = root
  gid = root
  read only = false
  hosts allow = xxx.xxx.xxx.xxx/32 10.0.0.0/24
  hosts deny = *
  list = true
  dont compress = *.gz *.tgz *.zip *.pdf *.sit *.sitx *.lzh *.bz2 *.jpg *.gif *.png *.JPEG *.jpeg *.mov *.MP4 .MOV

んで、startする。

$ sudo systemctl --now start rsync

Network Security Groupで、873/tcpを開けるのを忘れないように。

では、まず普通にrsyncしてみる。転送するのは34GBのデータ。

$ rsync -av --progress /opt/Data/ rsync://myrsyncserver.japaneast.cloudapp.azure.com:/Data/
sent 36,179,638,248 bytes  received 1,669 bytes  16,155,231.04 bytes/sec
total size is 36,170,802,020  speedup is 1.00

15.4MB/sしか出ない。圧縮したら速くなると考えるかもしれないけれど、rsyncd.confで設定している通り、そもそも圧縮されているファイル形式だと速くならないどころか、圧縮処理がオーバーヘッドになってむしろ遅くなる。

さて、今回の本題は、これを速くしたい。
でもrsyncは便利なので、他のプロトコルには変えたくない。
GNU Parallelrsyncを並列実行すれば、速くなる?でも、面倒くさいよね。
というわけで、Infinibandでの転送にも使われるparsyncfpを。要はrsyncのラッパーなので、サーバ側は何も変更しなくて良い。拙宅のサーバはRHEL 7なので、perl-Envとfpartをインストールし、parsyncfpをgit cloneして適当に配置する。

$ sudo yum-config-manager --add-repo https://copr.fedorainfracloud.org/coprs/survient/fpart/repo/epel-7/survient-fpart-epel-7.repo
$ sudo yum -y install fpart perl-Env
$ git clone https://github.com/martymac/fpart
$ sudo cp fpart/parsyncfp /usr/local/bin/
$ sudo chmod 755 /usr/local/bin/parsyncfp
$ sudo chcon -u system_u -r object_r -t bin_t -l s0 /usr/local/bin/parsyncfp
$ parsyncfp

parsyncfp version 1.57 
Mar 20, 2019
by Harry Mangalam >hjmangalam@gmail.com<

parsyncfp is a Perl script that wraps Andrew Tridgell's miraculous
......

使い方の詳細が表示されるので、適宜引数を調整するとして、上で転送した34GBのデータを転送してみる。
チャンクサイズとrsyncのフォーク数、加えて転送する個別のファイル容量やファイルシステムによって時間が変わるであろうことが推測出来るけど、チャンクサイズとフォーク数のみをマトリックスにしてみた。フォーク数32がどうやら安定して性能が出る様子。

下記は、チャンクサイズ32M、フォーク数16の例。

$ parsyncfp --rsyncopts="-a" --chunksize=32M --nowait --NP=16 --startdir='/opt/Data/' testdata rsync://myrsyncserver.japaneast.cloudapp.azure.com:/Data/
which: no perfquery in (/root/perl5/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin:/root/bin)
 WARN: About to remove all the old cached chunkfiles from [/root/.parsyncfp/fpcache].
  Enter ^C to stop this.
  If you specified '--nowait', cache will be cleared in 3s regardless.
  Otherwise, hit [Enter] and I'll clear them. 
 INFO: The fpart chunk files [/root/.parsyncfp/fpcache/f*] are cleared .. continuing.
 INFO: Forking fpart.  Check [/root/.parsyncfp/fpcache/fpart.log.14.49.42_2019-04-21] for errors if it hangs.
 INFO: Starting the 1st [32] rsyncs ..
        | Elapsed |   1m   |    [enp10s0]   MB/s  | Running || Susp'd  |      Chunks       [2019-04-21]
  Time  | time(m) |  Load  |     TCP / RDMA  out  |   PIDs  ||  PIDs   | [UpTo] of [ToDo]
14.49.52    0.05     0.04     408.66 / 0.00            32    <>   0          [32] of [50]
14.49.55    0.10     0.04     406.73 / 0.00            28    <>   0          [42] of [50]
14.49.59    0.17     0.04     424.60 / 0.00            24    <>   0          [50] of [50]
14.50.02    0.22     0.04     407.13 / 0.00            22    <>   0          [50] of [50]
......

14.53.52    4.05     0.28      19.22 / 0.00             1    <>   0          [50] of [50]
14.53.56    4.12     0.26      13.91 / 0.00             1    <>   0          [50] of [50]
14.53.59    4.17     0.23       9.49 / 0.00             1    <>   0          [50] of [50]

250秒で34GBを転送したので、139.2MB/s。単なるrsyncの9倍の速度。

実際にフルセットのデータ、1.1TBを転送したところ、119.57 minutesで、avg 151.88 MB/sだった。