前提

MongoDBのデータ領域がファイルシステム上30GB程度(wiredTigerのsnappyを使用)、gzip圧縮されたdumpファイルが10GB程度の場合、mongorestoreのパフォーマンスを高めるためにつけるべきオプション等を考える。

MongoDB 3.4.10 (Replica Set: Primary, Arbiter, Backup取得用Secondaryの3台構成)
CentOS 6.7
8core + 96GB memory + HDD

mongorestoreのオプション

パフォーマンスにかかわるオプションは次の3つ。

  • --writeConcern (Default: majority)
  • --numParallelCollections int (Default: 4) ※並列で扱うcollection数
  • --numInsertionWorkersPerCollection int (Default: 1) ※並列で扱うcollection内のinsertionWorker

writeConcern

writeConcernがmajorityだと、Replicat Setへのrestoreがかなり遅くなることが想定される。Replica Setの場合、writeConcernがmajorityだと、Secondaryへ書き込みが伝搬されるまでレスポンスを待つだけでなく、j: trueが暗黙的に設定されるためI/O負荷が高まる。

For replica sets using protocolVersion: 1, if journaling is enabled, w: "majority" may imply j: true. The writeConcernMajorityJournalDefault replica set configuration setting determines the behavior.

If j: true, requests acknowledgement that the mongod instances, as specified in the w: , have written to the on-disk journal.

https://docs.mongodb.com/manual/reference/write-concern/

numParallelCollections

numParallelCollectionsはデフォルトが4並列なので、特に変更する必要はなさそう。大量のCollectionがあるが一つ一つのCollectionがかなり小さい場合は、並列数を増やすことでスループットがあがるかもしれない。しかし大体のアプリケーションではどれか一つ突出して大量にデータを抱えたCollectionがあるもので、それが性能問題を起こしがちだから、Collectionの並列数を上げることで効果があるかは疑問。今回は検証しない。

numInsertionWorkersPerCollection

numInsertionWorkersPerCollectionはデフォルトが1で並列でないため、変更することで大幅に性能が変わりそう。大きいデータの場合には並列数を上げるといいとドキュメントに記載があるので、今回使用するデータサイズでは効果が見込めそうだ。

For large imports, increasing the number of insertion workers may increase the speed of the import.

https://docs.mongodb.com/manual/reference/program/mongorestore/

様々な組み合わせで検証する

numInsertionWorkersPerCollectionで並列数を上げる

並列数1

初回、1並列で実行したときの結果。

$ time sudo mongorestore localhost:27017/admin -uroot -p${PASSWORD} --oplogReplay --drop --writeConcern 1 --numInsertionWorkersPerCollection 1 --gzip --archive=/tmp/mongo.dump.gz

real    21m57.597s
user    10m29.286s
sys     0m49.897s

dumpファイルがメモリにキャッシュされることで所要時間が変わるかどうかを確認するためにもう一度実行したが、ほとんど変わらなかった。

以降、並列度を変更して実行する。

並列数8

まずはコア数と同じ8並列で実施。

$ time sudo mongorestore localhost:27017/admin -uroot -p${PASSWORD} --oplogReplay --drop --writeConcern 1 --numInsertionWorkersPerCollection 8 --gzip --archive=/tmp/mongo.dump.gz

real    9m57.879s
user    10m55.786s
sys     0m54.741s

8並列で書き込むとスピードがかなり向上した。restore中、LoadAverageが4~6程度だった。ioutilは時折100%に2~3秒張り付く状態だった。
サーバのリソース的には特に問題ないと判断できる。

並列数16

並列数を増やすとどうなるか。16並列だとLoadAverageが6~8程度だった。ioutilは時折100%に数秒はりつく。8並列と実行時間が変わらず、CPU, I/Oの観点からも8並列より増やしても意味がなさそう。

real    9m52.600s
user    11m2.429s
sys     0m56.613s

並列数4

4並列だとLoadAverageが3~5程度だった。ioutilはやはり時折張り付く。8並列よりは若干時間がかかっている。

real    11m21.003s
user    10m40.954s
sys     0m50.690s

サーバのリソース的には8並列より健全になっているものの、劇的に変わるわけではないのでrestoreスピードを重視して8並列がベストと考える。

writeConcernを変更する

writeConcern 1でPrimaryに更新がかかればレスポンスを返すように実施してきた。これを変更することでパフォーマンスがどれだけ変わるか確認する。

writeConcern 0

write operationの結果すら確認せずにレスポンスを返すことで、どれだけパフォーマンスが上がるか確認する。

$ time sudo mongorestore localhost:27017/admin -uroot -p${PASSWORD} --oplogReplay --drop --writeConcern 0 --numInsertionWorkersPerCollection 8 --gzip --archive=/tmp/mongo.dump.gz

real    9m59.099s
user    10m58.427s
sys     0m54.353s

writeConcern 1のときと変わらなかった。LoadAverage, ioutilも同様の数値だった。パフォーマンスが向上しないのであれば、writeConcernは1の方が安全なので1にする。

デフォルトのwriteConcern majority

$ time sudo mongorestore localhost:27017/admin -uroot -p${PASSWORD} --oplogReplay --drop --writeConcern majority --numInsertionWorkersPerCollection 8 --gzip --archive=/tmp/mongo.dump.gz

real    13m54.089s
user    11m7.585s
sys     0m56.057s

やはりwriteConcern 1のときより時間がかかった。サービス運用中であれば別として、restoreのときにSecondaryへの伝搬を待つ必要性は全くないので、より速いwriteConcern 1の方がいい。
ちなみに実行中のサーバリソースはLoadAverageが若干低かった。

gzipの与える影響

gzipしたdumpファイルを使ってrestoreしてきたが、これがパフォーマンスに影響を与えている可能性も考える。

圧縮されていないdumpを使ってrestoreする

伸長したら10GBが55GBになった。

$ time zcat /tmp/mongo.dump.gz > /disk/mongo.dump

real    5m22.161s
user    4m41.694s
sys     0m37.923s

ファイルサイズとディスク容量の問題で、MongoDBと同じディスクに置くことになったためディスクのread I/Oとwrite I/Oが同時に走ってしまい正確に計測できないが、とりあえず実行してみる。

$ time sudo mongorestore localhost:27017/admin -uroot -p${PASSWORD} --oplogReplay --drop --writeConcern 1 --numInsertionWorkersPerCollection 8 --archive=/disk/mongo.dump

real    13m47.283s
user    5m21.488s
sys     1m27.358s

伸長分の処理が減ったためLoadAverageは下がったが、io waitが高くて、やはり正確に計測できてなさそう。
timeのrealが伸びてしまったが、userがかなり下がっているのでディスクを分ければ効果がありそう。現実問題としてrestore用にディスクを追加できるとは限らないし、dumpファイルを圧縮しないで保存するのもコストがかかるので、gzipよりも性能の高い圧縮ライブラリで試してみる。

zstd

https://github.com/facebook/zstdを使う。

$ sudo yum install zstd --enablerepo=epel
$ time (sudo mongodump --host localhost:27017 -u root -p ${PASSWORD} --oplog  --archive | zstd > /tmp/mongo.dmp.zst)

dump時間はgzip圧縮付きで実行した場合30分かかっていたのが、11分で完了した。ファイルサイズも10GBが6.5GBになった。restore時間にも期待が持てる。

$ time (sudo zstdcat /tmp/mongo.dump.zst | mongorestore localhost:27017/admin -uroot -p${PASSWORD} --oplogReplay --drop --writeConcern 1 --numInsertionWorkersPerCollection 8 --archive)

real    8m14.625s
user    6m45.947s
sys     1m21.346s

かなり早くなったが、LoadAverageが8まであがり、ioutilも100%張り付くことがかなり増えた。

numInsertionWorkersPerCollectionの並列数を4に減らして実行してみる。
LoadAverageは5以下に下がったが、やはりI/Oがボトルネックとなっており時間は微増した。

real    8m53.271s
user    6m26.262s
sys     1m13.934s

並列数2だと処理時間にかなり影響を与える。

real    11m43.655s
user    6m14.018s
sys     1m12.043s

結論

データサイズやサーバ性能(CPU, DISK)により変わるものではあるが、--numInsertionWorkersPerCollectionは指定する方がいいことが多いと思われる。並列数はコア数と同じ~その半分くらいがいい。

--writeConcernは1に設定するべき。

圧縮に関して、--gzipを使った圧縮では効率があまり良くないためzstdなど他の圧縮ライブラリを使用した方がいい。