はじめに
今回は、MongoDBのレプリケーションについて説明します。
最初にレプリケーションの概要を説明します。次に実際に構築する手順を説明した後、レプリケーションに必要な設定項目について解説します。最後にMongoDBのレプリケーションで重要な機能であるOplog、書き込み保証などについて解説します。
前回の記事の最後に、シャーディングについて説明すると書きましたが、予定を変更しましてレプリケーションから先に説明します。シャーディングは次回取り上げる予定です。
レプリケーションのメリット
まずは、MongoDBが採用しているマスター/スレーブ方式のレプリケーションの一般的なメリットについて説明します。マスター/スレーブ方式のレプリケーションは以下のようなメリットをもたらします。
可用性の向上
レプリケーションは主に冗長性を得るために設計され、多くのプロダクション環境で導入されています。マスターノードがスレーブノードと同期していることから、マスターノードに障害が起きてもデータロストやサービス停止時間を最小限にすることが可能です。また近年ではDR(ディザスタリカバリ)対応のため、地理的に離れたデータセンターにノードを分散させる設計も多く見られます。
保守性の向上
レプリケーションは負荷の高い操作をスレーブノードで実行できることにより、メンテナンス性を高めてくれます。たとえばバックアップをスレーブノードで行い、不要な負荷をマスターノードにかけないようにすることは広く行われています。他にも負荷の高い大規模なインデックス構築なども、まずはスレーブノードで構築し、スレーブを既存のマスターと交代させ、その後新たなスレーブでもう一度インデックスを構築するといった方法を実施することが可能です。
負荷分散による性能の向上
レプリケーションを使用すれば、読み取り(Read)の負荷をノード間で分散させることができます。負荷のほとんどがReadで占められているようなアプリケーションの場合、Readの負荷をスレーブに分散させることによりシステムをスケールさせることが可能です。MongoDBではv2.2から、Readノードの分散をドライバで実装可能となりました。データの読み取り一貫性レベルに応じて、どのノードからReadするかをアプリケーションから容易に設定することが可能です。詳細は、本連載の第2回 MongoDB 2.2の新機能に記載されています。
MongoDBレプリケーション概要
MongoDBには多くのRDBが実装しているマスター/スレーブ方式のレプリケーション機能があります。レプリケーションは1つのMongoDBにあるデータを別のMongoDBに複製できる機能です。データの複製は非同期で行われます。従来のRDBに無い機能として、MongoDBは自動フェイルオーバーを実装しています[1]。マスターに障害が発生しても自動的にフェイルオーバーとリカバリが可能です。また、後ほど説明する「書き込み保証」や「読み取り負荷分散」はスケーラブルに設計されたMongoDBの特徴的な機能です。
MongoDBではマスターのことをプライマリ、スレーブのことをセカンダリと呼びます。MongoDBのレプリケーションの最小構成は、3つのノードが必要です。
- 2つのフルノードと1つのアービター(図1)
- 3つのフルノード(図2)
セカンダリは、プライマリのデータのコピーを保持します。アービターはデータのコピーを保持しませんが、フェイルオーバーの際に新しいプライマリノードを選択する役割を果たします。
MongoDBのレプリケーションには以下の2つがあります。
- マスター - スレーブレプリケーション
- レプリカセット
レプリカセットはv1.6から追加されたもので、マスター - スレーブレプリケーションの上位機能であり、自動化されたフェイルオーバー機能を提供します。現在ではレプリカセットが推奨されています。したがって、本記事ではレプリカセットについて解説します。
MongoDBのレプリケーションはレプリカセット機能で提供されます。それでは実際にレプリカセットを構築してみましょう。
レプリケーションを試してみよう
前のページで説明したように、MongoDBでレプリカセットを構成するには、最低3つのノードを含める必要があります。今回は、アービターを含めた3ノード構成で1台のPC上でレプリカセットを構築します。
それでは、レプリカセットをセットアップしましょう。まず、各ノードのデータディレクトリを作成します。
次に、各ノードのmongodを起動します。今回は同じマシン上で起動しましょう。
これらのノードは全て同じレプリカセットに所属させたいので、replSetには同じ値を指定します。これだけではレプリカセットを構成することはできず、さらに設定を加える必要があります。まず、いずれか1つのノードに接続します。
JSON形式でレプリカセットの設定を変数に格納し、rs.initiate()コマンドの引数として渡すことで、レプリカセットの初期化を行います。今後、JSON形式のレプリカセットの設定を「設定ドキュメント」と呼びます。今回は、設定ドキュメントをconfigという変数に格納します。
現在のレプリカセットの状態を、rs.status()コマンドで見てみましょう。
[参考]
rs.status()コマンドで確認できる、レプリカセットの各ノードが取りうる状態(stateStr)は表1の通りです。
各ノードのstateStrが"PRIMARY"、"SECONDARY"、"ARBITER"となっていることが確認できました。
以上の操作でレプリカセットが構築できました。
動作確認
レプリケーションの確認
作成したレプリカセットにデータを挿入し、実際にレプリケーションできるかどうかを確認します。まずは、プライマリであるポート番号30000のノードに、ドキュメントを10000件挿入してみます。
挿入したドキュメントがレプリケーションされているかどうか、セカンダリであるポート番号30001のノードから確認します。
このようなエラーが出力されました。プライマリ以外のノードからの読み込みを許可するために、setSlaveOk()コマンドを利用する必要があります。
セカンダリにも、10000件のドキュメントが格納されています。これで、レプリケーションの動作が確認できました。
フェイルオーバーの確認
プライマリノードのプロセスをkillし、フェイルオーバーが正しく機能するかどうか確認します。
ポート番号30001のノードのstateStrが"SECONDARY"から"PRIMARY"へと切り替わり、フェイルオーバーに成功しました。
リカバリの確認
リカバリは、フェイルオーバーの際にkillしたノードを立ち上げるだけで可能です。
30000番ポートのノードがセカンダリとしてリカバリしたことが確認できました。
次ページでは、レプリケーションのオプションについて解説します。
MongoDBのコンフィグに含まれるレプリケーションオプション
MongoDBのコンフィグの中には、以下のようなレプリケーションに関するオプションも含まれます。
表2 レプリケーションオプション一覧(※2)
オプション | 内容 |
replSet | レプリカセットの名称を指定します。レプリカセットに参加する全てのノードに同じ値を指定しなければなりません。 |
oplogSize | OplogのサイズをMB単位で指定します。64ビットOSの場合、ディスクスペースの5%がデフォルト値です。一旦mongodがOplogを作成してしまうと、このオプションを使ってOplogのサイズを変更することはできません。Oplogに関しては次のページで説明します。 |
fastsync | このオプションをtrueにすると、レプリケーションに参加している他ノードのdbpath以下のスナップショットを元にレプリケーションを行います。ただし、プライマリとセカンダリ間のデータの整合性が取れていない状態でこのオプションをtrueにしてmongodを起動した場合、レプリケーションが機能しなくなり同期できなくなるので注意してください。 |
replIndexPrefetch | セカンダリはOplogを実行する前に、オペレーションに必要なインデックスをメモリ上にロードします。このオプションにデフォルトの"all"が指定されていた場合は全てのインデックスをロードしますが、"none"が指定された場合はインデックスをロードしません。"_id_only"が指定された場合は、_idのインデックスのみロードします。 replSetオプションが指定されている場合のみ、このオプションを利用できます。 |
設定ドキュメントのオプション
設定ドキュメントにより、レプリカセットの各ノードを表3に示す種類に分類することができます。
表3 ノードの種類
種類 | 状態 |
Secondary-Only | セカンダリとしてのみ機能するノードです。プライマリになることはありません。 |
Hidden | プライマリにならず、クライアントアプリケーションからも隠ぺいされたノードです。ただし、プライマリ選出時の投票には参加します。 |
Delayed | プライマリからのデータコピーを遅らせたノードです。プライマリになることはありません。このノードは、ユーザの操作ミス等に対する保険的な利用が可能です。 例えば、遅延時間を1時間に指定していたとき、管理者が誤ってデータベースを削除してしまった場合でも、1時間以内にこのノードを切り離せば、データを保全することができます。 |
Arbiters | プライマリ選出時の投票に参加するノードです。データのコピーを保持せず、プライマリになることはありません。 |
Non-Voting | プライマリ選出時の投票権を持たないノードです。 |
設定ドキュメントの書き方は、公式ドキュメントで次のように示されています。
設定ドキュメントは、ノードのオプションと、レプリカセット全体のオプションを指定できます。各オプションの内容を、表4および表5に示します。
表4 ノードのオプション
オプション | 内容 |
_id | 必須のオプションです。ノードのIDを指定します。0から始まり、ノードが追加されるごとに1ずつインクリメントしなければなりません。 |
host | 必須のオプションです。ノードのホスト名とポート番号を指定します(例:「member1:30000」)。ホスト名は、IPアドレスでも構いません。ポート番号を指定しない場合は、デフォルトのポート番号(27017)が使われます。 |
arbiterOnly | ノードがアービターであるかどうかを、trueまたはfalseで指定します。 |
priority | 0~1000までの整数値で、優先度を指定します。数値が大きいほど優先度が高く、プライマリに選出されやすくなります。0を指定した場合、プライマリに選出されなくなります。 |
votes | プライマリ選出のための投票権の数を指定します。投票権の数のデフォルトは1です。特別な理由が無い限り、デフォルトのままで構いません。 |
hidden | ノードを隠蔽するかどうかを、trueまたはfalseで指定します。trueで隠蔽されたノードは、db.isMaster()コマンドから参照できなくなり、その結果、プライマリに選出されなくなります。 |
buildIndexes | インデックスを作成するかどうかを、trueまたはfalseで指定します。デフォルトはtrueです。このオプションはバックアップとして利用されるノードのために設計されたものであり、インデックスのバックアップが必要ない場合、プライマリに選出されないノード(priorityが0またはhiddenがtrueのノード)に限って利用すべきです。 |
slaveDelay | プライマリに選出されないノード(priorityが0またはhiddenがtrueのノード)に限り、プライマリからのデータコピーの遅延を秒数で指定できます。 |
tags | 「キー:値」をセットとした、任意のタグを付加できます。通常、タグでデータセンターやラックの位置を指定し、後で説明するgetLastErrorModesオプションと組み合わせて、書き込み保障や読み取り設定のモードを定義するために利用します。 |
表5 レプリカセット全体のオプション
オプション | 内容 |
getLastErrorDefaults | getLastErrorコマンドのデフォルトの引数を指定します。 getLastErrorコマンドについては、公式ドキュメントのCommand Referenceを参考にしてください。 |
getLastErrorModes | tagsオプションと組み合わせて、書き込み保障や読み取り設定のモードを定義します。次のページで、詳しく解説します。 |
レプリケーションでの重要項目
本記事の最後として、MongoDBのレプリケーションで重要な項目について説明します。
- レプリケーションの仕組み(Oplog)
- 書き込み保証
- 読み取りスケーリング
- 書き込み/読み取りのタグ付けによる制御
レプリケーションの仕組み(Oplog)
Oplogはレプリケーションに必要なデータ操作オペレーションの集合です[3]。実態は、レプリケーションに参加している全てのノードにあるlocalというデータベースの中にあるoplog.rsという名前のCapped Collection[4]です。クライアントがプライマリへのデータ操作オペレーションを行う度に、そのオペレーションを再現するのに必要な情報を持つエントリが、自動的にプライマリのOplogに追加されます。セカンダリはプライマリのOplogの変更分をコピーし、オペレーションを実行することで同期します。セカンダリは自身が適用した最後のエントリをタイムスタンプで管理しています。セカンダリは一定期間ごとに、プライマリのOplogをチェックし、更新分があればコピーしてオペレーションを実行します。
Oplogに保存されているもの
前ページで構築したレプリケーション環境を使って、Oplogを実際に見て確認してみましょう。プライマリに接続してデータをinsertした後、localデータベースに接続してoplog.rsをfindします。
2つのドキュメントが表示されますが、2つ目がinsertオペレーションのエントリです。1つ目は初期化時に保存されるオペレーション無しのエントリです。oplog.rsの各フィールドに保存されているデータは表6のようになっています。
表6 oplog.rsのフィールド
フィールド名 | 保存されているデータ |
ts | オペレーションのBSONタイムスタンプ |
h | オペレーションのユニークなID |
op | オペレーションの種類。"i" insert,"u" update,"d" delete,"c" dbコマンド,"n" オペレーション無しなどがある |
ns | ネームスペース。データベース.コレクションで表記される。 |
o | オペレーションで使用されたドキュメントのコピー。 |
Stale問題
プライマリ/セカンダリ間でOplogの同期が追いつかない場合、Stale問題が発生します。前述したようにOplogはCapped Collectionsです。サイズの上限に達すると、古いドキュメントが削除されます。書き込みオペレーションが非常に多いシステムでは、セカンダリがプライマリのOplogを同期する前に、プライマリのOplogが削除される可能性があります。その場合、mongodは以下のエラーメッセージを出して停止します。
停止してしまったレプリケーションに対する解決策は、一般的には完全に同期し直すことしかありません。Staleを避けるために、書き込みの量に対して十分な大きさのOplogを用意しておくこと必要があり、Oplogのサイジングは重要な設計項目となります。
Oplogのサイジング
OplogはCapped Collectionなので、作成時以外にサイズ変更することができません。デフォルトのOplogのサイズは表7のようになります。
表7 Oplogのデフォルトサイズ
システム | デフォルトサイズ |
32bit | 50MB |
64bit | 1GBまたは、ディスクの空き領域の5% |
Mac OS X | 192MB |
Staleを避けるために、Oplogのサイジングは非常に重要となります。Oplogのサイズは、mongodの初回起動時にoplogSizeオプション[5]で変更可能です。
Oplogの適切なサイズを見積もる方法のひとつに、本番を想定した書き込みテストを実施し、作成されたOplogのサイズを取得する方法があります。1時間程度、本番想定と同程度の書き込みテストを行った後、以下のコマンドでレプリケーションの最新情報を取得します。
1時間で作成されたOplogのサイズがわかれば、Oplogのサイジングの目安となります。少なくとも8時間のセカンダリのダウンタイムに耐えられるようにしておくことが推奨されています。
書き込み保証
クエリにオプションを指定することによって、書き込み時のリターンを返すレベルを設定し、書き込み保証を実施することができます。書き込み保証は図3および表8に示すとおり、5段階あります。
表8 書き込み保証オプション
オプション | 書き込み保証レベル |
デフォルト | 無し(非同期) |
w = 1 | メモリ |
w = 2 | 最低でも1つのサーバにレプリケーション |
j:true | ジャーナル(ディスク) |
w = majority | レプリケーションに参加しているノードの過半数 |
w = "タグ" | タグで指定したノード |
たとえば、ジャーナリングを有効にし、最低でも1つのサーバにレプリケーションさせる場合、Rubyでは以下のようにinsertします。
読み取り負荷分散
MongoDBのレプリケーションでは、アプリケーションからプライマリ/セカンダリの、どのノードから読み取り(Read)するかを設定できます。データの一貫性レベルに応じて、クエリごとに設定することが可能です。設定レベルは表9に示すとおり、5つあります。
表9 読み取り設定
設定名 | 意味 |
PRIMARY | PRIMARYノードのみからReadする |
PRIMARY PREFERRED | 可能であればPRIMARYノードからReadする |
SECONDARY | SECONDARYノードのみからReadする |
SECONDARY PREFERRED | 可能であればSECONDARYノードからReadする |
NEAREST | レイテンシの小さいノードからReadする |
必ず最新のデータが必要な場合には、強い一貫性を保証する"PRIMARY"を、結果整合性で良い場合はそれ以外をレベルに応じて設定可能です(図4)。
Rubyでの設定例は以下のようになります。
書き込み/読み取りのタグ付けによる制御
tagsオプションとgetLastErrorModesを組み合わせることで、書き込み保障や読み取り設定のモードを定義し、書き込み/読み取り先を指定することができます。たとえば、以下のような設定ドキュメントにした場合、少なくとも2つのデータセンターにまたがるレプリケーションを保障するモード"multiDC"を定義することができます。dcタグはデータセンターの場所を表しています。
定義したモード"multiDC"を利用するには、Rubyでは以下のようにします。
次回のテーマ
今回はMongoDBが備えているレプリケーション機能について説明しました。MongoDBはスケールアウトを前提に設計されているので、書き込み/読み取りの負荷分散に関する高度な設定も可能です。一方で、書き込み負荷の大きな環境で使用するにはOplogに関する理解が必要ですが、適切に設定を行えば高い可用性を実現できます。
次回はMongoDBのもうひとつのスケールアウトの仕組みであるシャーディングについて説明します。