読者です 読者をやめる 読者になる 読者になる

中年engineerの独り言 - crumbjp

LinuxとApacheの憂鬱

MongoDBのデータファイル同期について

MongoDBのストレージエンジン

ココで触れた通り
MongoDBは書き込みリクエストを受け付けると、一旦、内部キューにデータを積み、
バックグラウンドスレッド(journal)によって、journalファイルとデータファイルのmmap領域に書き込まれる。
その後、更にバックグラウンドスレッド(DataFileSync)によってmsync()され無事にファイルに書き込まれる事になっている

この書き込む動作をwriteback呼ぶ

しかし、Linuxでは、mmap()領域はmsync()せずともkernelが一定間隔で書き込んでくれる。

今回は、この辺の挙動の話。

MongoDBのwriteback

mongodの起動オプション--syncdelay=<秒> によって指定できる。
デフォルト値は60
0を指定するとmsync()を呼ばない挙動になる。

//docs.mongodb.org/manual/reference/configuration-options/#journal">マニュアル:If you set syncdelay to 0, MongoDB will not sync the memory mapped files to disk. Do not set this value on production systems.
syncdelayを0にするとディスクには書かないから本番では指定するなよ。

ここまで来るとまるっきり嘘である!

以前、syncdelayのドキュメントのミスを指摘したのだが
更に輪を掛けて酷くなったのでついカッとなってこのエントリーを書いた。

多分彼らもこの辺の挙動を正確に理解してないのだな。

Linuxのwriteback

主に以下のパラメータで制御されている。

vm.dirty_expire_centisecs
デフォルト3000(=30秒)。
ディスクに書き込まれるまでの猶予期間。
vm.dirty_writeback_centisecs
デフォルト500(=5秒)。
実際にディスクに書き込む処理の起動間隔。

それぞれこんな感じで確認できる。(直接/proc以下を読み書きしても可)

$ sysctl vm.dirty_expire_centisecs
vm.dirty_expire_centisecs = 3000
$ sysctl vm.dirty_writeback_centisecs
vm.dirty_writeback_centisecs = 500

他にも以下の様な値があり、トータルメモリーに占める割合やら、バイト数の閾値でも制御できるが、今回はパス。

  • vm.dirty_background_ratio
  • vm.dirty_background_bytes
  • vm.dirty_ratio
  • vm.dirty_bytes
  1. Linuxではメモリーはある大きさ毎にページと呼ばれる単位で管理されている。
  2. プログラムがmmapしたメモリー領域に更新を行った時、直ちにディスクに書き込むとパフォーマンスが出ない。
    そこで更新された領域を含むページにフラグだけ立てて後で纏めてディスクに書き込む戦略を取っている。
    このフラグの事をDirtyフラグと呼ぶ。
  3. いつまでもディスクに書き込まないでいると今度は不慮の事故が起きた時にデータが失われてしまうので
    適度な間隔でDirtyフラグの立ったページをチェックし一定期間経ったページをディスクに書き込む。writeback
    (ディスクに書いたページはDirtyフラグを解除する)

問題

MongoDBのデフォルトのwriteback間隔は60秒、Linuxのwritebackは最低でも30秒以内には動く。
だからMongoDBのwriteback専用スレッド(DataFileSync)やsyncdelayオプションなんて要らないじゃん!
だったらいっその事、デフォルトsyncdelay=0としてOSに任せた方が良くね?
という話を投げてみた。

これも時間かかるだろうな・・・

syncdelay=0とした場合の挙動を確かめた

1.syncdelay=0を指定してmongodを上げる。
2.毎秒一回、適当なコレクションを更新する。

for i in {0..10000}; do
mongo 127.0.0.1:27017 <<< "use testdb
db.testcol.save({key:$i})";
sleep 1
done;

3.データファイルのstatを取り、更新時刻を確認する。

stat data/testdb/testdb.0 -c'%y'
結果1(デフォルト)
パラメータ
vm.dirty_expire_centisecs = 3000
vm.dirty_writeback_centisecs = 500
2013-03-13 00:20:57.441599513 -0700
2013-03-13 00:21:04.838052872 -0700
2013-03-13 00:21:29.125808067 -0700
2013-03-13 00:21:34.408625047 -0700
2013-03-13 00:21:42.857941700 -0700
2013-03-13 00:21:59.761572768 -0700
2013-03-13 00:22:00.817927798 -0700
2013-03-13 00:22:28.286594599 -0700
   :

バラツキはあるが、30秒近く更新されないケースもある。

結果2(5秒間隔でwriteback)
パラメータ
vm.dirty_expire_centisecs = 500
vm.dirty_writeback_centisecs = 100
# sysctl vm.dirty_expire_centisecs=500
# sysctl vm.dirty_writeback_centisecs=100
2013-03-13 00:23:09.481541037 -0700
2013-03-13 00:23:14.760541494 -0700
2013-03-13 00:23:20.040029026 -0700
2013-03-13 00:23:24.267557606 -0700
2013-03-13 00:23:29.551242097 -0700
2013-03-13 00:23:34.832939305 -0700
2013-03-13 00:23:35.888281140 -0700
2013-03-13 00:23:40.109627939 -0700
2013-03-13 00:23:45.389313622 -0700
2013-03-13 00:23:50.671000401 -0700
2013-03-13 00:23:51.726337540 -0700
   :

大体5秒毎に更新されている

結果2(1秒間隔でwriteback)
パラメータ
vm.dirty_expire_centisecs = 100
vm.dirty_writeback_centisecs = 100
# sysctl vm.dirty_expire_centisecs=100
# sysctl vm.dirty_writeback_centisecs=100
2013-03-13 00:28:44.298270234 -0700
2013-03-13 00:28:45.353080480 -0700
2013-03-13 00:28:46.409875411 -0700
2013-03-13 00:28:47.467720763 -0700
2013-03-13 00:28:48.527613888 -0700
2013-03-13 00:28:49.583300450 -0700
2013-03-13 00:28:50.640146646 -0700
2013-03-13 00:28:51.697976987 -0700
2013-03-13 00:28:52.754726703 -0700
2013-03-13 00:28:53.809552595 -0700
  :

毎秒に更新されている模様。