本記事は中級編ですので、既にレプリケーションを組んだことのある読者を対象とし、基本的な説明は割愛させていただきます。
レプリケーションはスケールアウトの手段ではない
MongoDBはレプリケーション構成においてセカンダリからも読み込むことにより読み取り負荷分散が出来ます。ですが、勘違いしてはいけないのは、レプリケーションは高可用の手段でありスケールアウトの手段ではないとうことです。
これはよく勘違いされて、読者の中には「読み取りが遅いからレプリケーションの読み取り負荷分散で速くしよう」と考えている人はいないでしょうか?その考え方は注意が必要です。
そもそも「速さ」とはなんでしょうか?「速さ」といっても、それは2つの意味があります。ひとつはクエリの応答時間が短くなることで、これはターンアラウンドタイム(TAT)が速くなるといいます。もう一つはシステム全体で単位時間あたりの処理件数が増えることで、これはスループットが向上するといいます。
今回は、まずはじめにレプリケーションと読み取り負荷分散の動作の説明をします。その上で、これら2つの速さについて考察し、場合によっては速くならないということを説明します。また、読み取り負荷分散を行った時の弊害についても説明します。
レプリケーションの仕組み
MongoDBのレプリケーションは、プライマリの更新ログをセカンダリが読み取り自身に適用することにより実現されています。プライマリへの書き込みとセカンダリの書き込みは同期していないため「非同期レプリケーション」に分類されます。
更新ログの実態は、レプリケーションされない特別なデータベース「local」の中にあるキャップ付きコレクション「oplog」です。ここに全てのデータベースに対する更新内容やインデックス生成などのコマンドが記録されます。
以下の図では更新クエリがレプリケーションされる様子を示しています。
まずセカンダリは、自身のoplogのタイムスタンプを見てそれ以降のoplogを要求します。その後プライマリが更新した内容をoplogに書き込んだタイミングで、セカンダリの要求に応答します。これをロングポーリングと言います。これによりほぼ遅延なくセカンダリにoplogが転送され、セカンダリはそれを自身のDBに反映しoplogにも書き込みます。
読み取り負荷分散の仕組み
クエリにRead Preferenceモードを指定することにより、プライマリ以外のノードからも読み取りをすることができます。Read Preferenceモードには5つのモードが有ります。
- primary: プライマリからのみ読み込み(デフォルト)
- primaryPreferred: プライマリが利用できない時は近いセカンダリからランダム読み込み
- secondary: 近いセカンダリからランダム読み込み
- secondaryPreferred: セカンダリが利用できない時はプライマリから読み込み
- nearest: 近いノードでランダム読み込み
ここで「近い」と「ランダム」と書いてあるのには明確な定義があり、デフォルトでping応答が15ms秒以内のノードは「最も近い」と一律で定義され、その中からランダムで読み込む選択されます。よって「nearest」だからといって最も近いノード一つのみから読み取るわけではないです(紛らわしい名前なので筆者は変えたほうが良いと思っています)。この動作はMongoDBドライバによって行われます。mongod側の設定ではありません。詳しくは公式マニュアルのRead Preference Processeを参照してください。
うまく読み取り負荷分散されているかは、mongostatに--discoverオプションをつけると簡単に観測できます。--discoverオプションでは、レプリカ内の一台に対してmongostatを発行すれば、それ以外のレプリカセットのメンバの状態も同時に表示してくれます。以下の実行例では、読み取りが520,533,552と均等に分散されていることがわかります。
$ mongostat --discover -h 192.168.1.7:27017 insert query update delete ... set repl time 192.168.1.7:27017 250 520 *0 *0 ... myrs PRI 01:43:57 #←プライマリ 192.168.1.8:27017 *251 533 *0 *0 ... myrs SEC 01:43:57 #←セカンダリ1 192.168.1.9:27017 *272 552 *0 *0 ... myrs SEC 01:43:57 #←セカンダリ2
うまくリクエストが分散されない場合は、ping応答が15ms以上かかるノードが混在していないか確認して下さい。そうなっている場合は、ドライバのしきい値を15msから長くすれば全てが「最も近い」と判定されて均等にリクエストが分散できるでしょう。
また、タグセットという機能を使うことにより、レプリカ一台ごとにタグを付与し、クエリでタグを指定することで、より柔軟に読み込みの分散を行うことができます。詳しくは公式マニュアルのTag Setを参照してください。