Edited at
MongoDBDay 7

MongoDBのレプリカセットのフォールバックができなくてハマった話

More than 3 years have passed since last update.

MongoDB Advent Calendar 2014 の7日目です :santa:

6日目は naruoga さんの 2014年のMongoDB JPを振り返る でした。

今回は、MongoDB の Replica Set へ接続する際、Connect Timeout MS を適切に設定しましょうという話をします。


事案

MongoDB2.4, PHPDriver 使用時にて。

サーバー3台で Replica Set を組んだところ、1台のサーバーインスタンスがダウンすると通常は残りの2台でのフォールバック(縮退運転)状態で稼働するはずが、フェイルオーバーは成功するにもかかわらず、アプリケーション側からはまともに読み書きできなくなるという事案が発生した。(エラーは出ないが異常に遅い)

アプリケーションはPHPのウェブアプリだったため、PHPDriverのマニュアルJiraチケットを読み漁ったが解決せず、最終的には Driverのソースを読んだところMongoDBへの接続オプションの問題とあたりをつけることができ、試してみたところあっさり解決した。


問題の詳細

まず Replica Set が3台構成で組まれて正常に動作している時、1台のサーバーがダウンした際、MongoDB 自体のフェイルオーバーは正常に成功する。また、PHPDriverの設定(php.ini)を適切に設定すれば、PHPからの接続先の繋ぎ換えも正常に動作する。

しかし、肝心のPHPアプリケーションからのDBの読み書きが一向に終了しない。特にエラーが上がるわけではないが、DBアクセス部分で引っかかっていて処理がブロックされる。

そして、サーバー自体がダウンした時はこの現象が発生するが、mongod プロセスを落としただけの時は問題なく動作する。

パケットロスした時のみに発生する問題と、あたりをつけることができた。


Connection Timeout

MongoDBへの接続時、接続の設定をオプションとして渡すことができる。このオプションの中で、


  • Connect Timeout MS

  • Socket Timeout MS

という二つのタイムアウト設定がある。

MongoDB のドキュメントの Connection String URI Format によると、

タイムアウト種別
動作

Connect Timeout MS
コネクション自体のタイムアウト時間(ms)。デフォルトはタイムアウトなし

Socket Timeout MS
Socket通信自体のタイムアウト時間(ms)。デフォルトはタイムアウトなし

となっている。これは以下のように接続時に指定できる。

$mc = new MongoClient(

"mongodb://rs1.example.com:27017,rs2.example.com:27017,rs3.example.com/?replicaSet=testRs&connectTimeoutMS=3000"
);

そして、これら設定の厳密な動作の内容はDriver依存であり、Mongoコアのドキュメントからも Driver のドキュメントを読むようにとリンクが貼ってある。なぜなら、これらのオプションを処理するのは MongoDB のクライアントに当たる Driver だからである。


PHPDriver では

PHPドライバのIOストリーム関連のソースコード上(1.5.8)では、ストリームを生成するまで Connect Timeout MS を利用し、ストリーム生成後に条件に応じて Socket Timeout MS の数値を再設定している。

ちなみに Pythonのドライバ(2.7.2)でも同様に、コネクション確立直後にソケットのタイムアウト設定を変更する処理を行っている。ただしこちらでは、デフォルトの Connect Timeout MS が 20000 となっているので注意が必要。


Outro

MongoDBは比較的、本体よりも Driver 周りでハマることが多いです。

MongoDBを利用したアプリケーションを設計する際、Driverの品質やクセからアプリケーションの記述言語を選定するというアプローチも有効だと思います。

完全に私見ですが、PHPDriver を他の言語 Driver と比較すると


  • ドキュメントは比較的整備されている(翻訳もしっかり)

  • PHP という言語の(旧来の*1)モデルと MongoDB のモデルがミスマッチ


    • 連想配列ドリブンなコーディング VS カーソルに対するイテレータベースなコーディング

    • 手続き型的なPHP VS オブジェクト指向的なMongoPHPDriver



*1: 最近のPHPはイテレータを使うし、標準ライブラリもオブジェクト指向的になっている。ただ、プログラマがその変化について行けてないんじゃないかな、と思う。大多数のPHPプログラマにとって、オブジェクト指向は特に必要なものではないみたいで。PHP側も、オブジェクト指向的な標準ライブラリの手続き型的なシンタックスシュガーを用意していたりする。

などなど、敷居は低いがちょっと癖ある感じです。PHP を利用する明確な理由がない限り、他の言語を選定するのが得策な気がします。

ちなみにPHPDriverに対して以下の感想をずっと持っていたんですが、他の言語用 Driver も大差ないことが今回の調査でわかりました。


  • 仕様変更が多い

  • ソースコードが読みにくい(なんというかメソッド長くてネスト深くて解読に時間がかかる)

PHP, Python, Node.js, Ruby, Java, Scala用とざっと読みましたが、Rubyが一番読みやすく感じました。Rubocopの強制力の効果だと思います。ワーストは、、Javaかな。

Scalaはえらくシンプルかつ具体的な処理が見当たらないのでびっくりしましたが、Java用のドライバ依存でした。

明日は ak2iさんのObjectの生簀アーキテクチャとMongoDBについてです :raised_hand: <- ハイタッチてきな