前提
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
など他の圧縮ライブラリを使用した方がいい。