はじめに
今回は、
最初にレプリケーションの概要を説明します。次に実際に構築する手順を説明した後、
前回の記事の最後に、
レプリケーションのメリット
まずは、
可用性の向上
レプリケーションは主に冗長性を得るために設計され、
保守性の向上
レプリケーションは負荷の高い操作をスレーブノードで実行できることにより、
負荷分散による性能の向上
レプリケーションを使用すれば、
MongoDBレプリケーション概要
MongoDBには多くのRDBが実装しているマスター/スレーブ方式のレプリケーション機能があります。レプリケーションは1つのMongoDBにあるデータを別のMongoDBに複製できる機能です。データの複製は非同期で行われます。従来のRDBに無い機能として、
MongoDBではマスターのことをプライマリ、
- 2つのフルノードと1つのアービター
(図1) - 3つのフルノード
(図2)


セカンダリは、
MongoDBのレプリケーションには以下の2つがあります。
- マスター - スレーブレプリケーション
- レプリカセット
レプリカセットはv1.
MongoDBのレプリケーションはレプリカセット機能で提供されます。それでは実際にレプリカセットを構築してみましょう。
レプリケーションを試してみよう
前のページで説明したように、
それでは、
$ mkdir /data/node1 $ mkdir /data/node2 $ mkdir /data/node3
次に、
$ mongod --dbpath=/data/node1 --replSet=myrep --port=30000 --fork --logpath /var/log/mongodb30000.log --logappend $ mongod --dbpath=/data/node2 --replSet=myrep --port=30001 --fork --logpath /var/log/mongodb30001.log --logappend $ mongod --dbpath=/data/node3 --replSet=myrep --port=30002 --fork --logpath /var/log/mongodb30002.log --logappend
※
これらのノードは全て同じレプリカセットに所属させたいので、
$ mongo --port=30000 MongoDB shell version: 2.2.2 connecting to: 127.0.0.1:30000/test
JSON形式でレプリカセットの設定を変数に格納し、
> config = { _id : "myrep", members : [ { _id : 0, host : "[IPアドレス]:30000" }, { _id : 1, host : "[IPアドレス]:30001" }, { _id : 2, host : "[IPアドレス]:30002", arbiterOnly : true } ] } { "_id" : "myrep", "members" : [ { "_id" : 0, "host" : "[IPアドレス]:30000" }, { "_id" : 1, "host" : "[IPアドレス]:30001" }, { "_id" : 2, "host" : "[IPアドレス]:30002", "arbiterOnly" : true } ] } > rs.initiate(config)
※ [IPアドレス]は、
※ rs.
※ 30002番ポートのノードは、
現在のレプリカセットの状態を、
> rs.status() { "set" : "myrep", "date" : ISODate("2013-01-03T10:46:59Z"), "myState" : 1, "members" : [ { "_id" : 0, "name" : "127.0.0.1:30000", "health" : 1, "state" : 1, "stateStr" : "PRIMARY", "uptime" : 80, "optime" : Timestamp(1357209986000, 1), "optimeDate" : ISODate("2013-01-03T10:46:26Z"), "self" : true }, { "_id" : 1, "name" : "127.0.0.1:30001", "health" : 1, "state" : 2, "stateStr" : "SECONDARY", "uptime" : 30, "optime" : Timestamp(1357209986000, 1), "optimeDate" : ISODate("2013-01-03T10:46:26Z"), "lastHeartbeat" : ISODate("2013-01-03T10:46:58Z"), "pingMs" : 1 }, { "_id" : 2, "name" : "127.0.0.1:30002", "health" : 1, "state" : 7, "stateStr" : "ARBITER", "uptime" : 30, "lastHeartbeat" : ISODate("2013-01-03T10:46:58Z"), "pingMs" : 1 } ], "ok" : 1 }
※ 実際は接続すると"myrep:PRIMARY>"と表示されますが、
※ Webインターフェース
[参考]
rs.各ノードのstateStrが"PRIMARY"、
以上の操作でレプリカセットが構築できました。
動作確認
レプリケーションの確認
作成したレプリカセットにデータを挿入し、
$ mongo --port=30000 MongoDB shell version: 2.2.2 connecting to: 127.0.0.1:30000/test > use mydb switched to db mydb > for(var i=0; i
挿入したドキュメントがレプリケーションされているかどうか、
$ mongo --port=30001 MongoDB shell version: 2.2.2 connecting to: 127.0.0.1:30001/test > use mydb switched to db mydb > db.logs.count() Thu Jan 03 20:06:09 uncaught exception: count failed: { "errmsg" : "not master", "note" : "from execCommand", "ok" : 0 }
このようなエラーが出力されました。プライマリ以外のノードからの読み込みを許可するために、
> db.getMongo().setSlaveOk() > db.logs.count() 10000
セカンダリにも、
フェイルオーバーの確認
プライマリノードのプロセスをkillし、
$ kill -9 [プライマリのプロセス番号] $ mongo --port=30001 MongoDB shell version: 2.2.2 connecting to: 127.0.0.1:30001/test > rs.status() { "set" : "myrep", "date" : ISODate("2013-01-03T11:19:30Z"), "myState" : 1, "members" : [ { "_id" : 0, "name" : "127.0.0.1:30000", "health" : 0, "state" : 8, "stateStr" : "(not reachable/healthy)", "uptime" : 0, "optime" : Timestamp(1357210792000, 1822), "optimeDate" : ISODate("2013-01-03T10:59:52Z"), "lastHeartbeat" : ISODate("2013-01-03T11:19:11Z"), "pingMs" : 0, "errmsg" : "socket exception [CONNECT_ERROR] for 127.0.0.1:30000" }, { "_id" : 1, "name" : "127.0.0.1:30001", "health" : 1, "state" : 1, "stateStr" : "PRIMARY", "uptime" : 2036, "optime" : Timestamp(1357210792000, 1822), "optimeDate" : ISODate("2013-01-03T10:59:52Z"), "self" : true }, { "_id" : 2, "name" : "127.0.0.1:30002", "health" : 1, "state" : 7, "stateStr" : "ARBITER", "uptime" : 1974, "lastHeartbeat" : ISODate("2013-01-03T11:19:29Z"), "pingMs" : 0 } ], "ok" : 1 }
ポート番号30001のノードのstateStrが"SECONDARY"から"PRIMARY"へと切り替わり、
リカバリの確認
リカバリは、
$ mongod --dbpath=/data/node1 --replSet=myrep --port=30000 $ mongo --port=30000 MongoDB shell version: 2.2.2 connecting to: 127.0.0.1:30000/test > rs.status() { "set" : "myrep", "date" : ISODate("2013-01-03T11:33:09Z"), "myState" : 2, "syncingTo" : "127.0.0.1:30001", "members" : [ { "_id" : 0, "name" : "127.0.0.1:30000", "health" : 1, "state" : 2, "stateStr" : "SECONDARY", "uptime" : 20, "optime" : Timestamp(1357210792000, 1822), "optimeDate" : ISODate("2013-01-03T10:59:52Z"), "errmsg" : "syncing to: 127.0.0.1:30001", "self" : true }, { "_id" : 1, "name" : "127.0.0.1:30001", "health" : 1, "state" : 1, "stateStr" : "PRIMARY", "uptime" : 20, "optime" : Timestamp(1357210792000, 1822), "optimeDate" : ISODate("2013-01-03T10:59:52Z"), "lastHeartbeat" : ISODate("2013-01-03T11:33:09Z"), "pingMs" : 0 }, { "_id" : 2, "name" : "127.0.0.1:30002", "health" : 1, "state" : 7, "stateStr" : "ARBITER", "uptime" : 20, "lastHeartbeat" : ISODate("2013-01-03T11:33:09Z"), "pingMs" : 0 } ], "ok" : 1 }
30000番ポートのノードがセカンダリとしてリカバリしたことが確認できました。
次ページでは、
MongoDBのコンフィグに含まれるレプリケーションオプション
MongoDBのコンフィグの中には、
オプション | 内容 |
---|---|
replSet | レプリカセットの名称を指定します。レプリカセットに参加する全てのノードに同じ値を指定しなければなりません。 |
oplogSize | OplogのサイズをMB単位で指定します。64ビットOSの場合、 |
fastsync | このオプションをtrueにすると、 |
replIndexPrefetch | セカンダリはOplogを実行する前に、 replSetオプションが指定されている場合のみ、 |
設定ドキュメントのオプション
設定ドキュメントにより、
種類 | 状態 |
---|---|
Secondary-Only | セカンダリとしてのみ機能するノードです。プライマリになることはありません。 |
Hidden | プライマリにならず、 |
Delayed | プライマリからのデータコピーを遅らせたノードです。プライマリになることはありません。このノードは、 例えば、 |
Arbiters | プライマリ選出時の投票に参加するノードです。データのコピーを保持せず、 |
Non-Voting | プライマリ選出時の投票権を持たないノードです。 |
設定ドキュメントの書き方は、
{
_id : <setname>,
version: <int>,
members: [
{
_id : <ordinal>,
host : hostname<:port>,
<arbiterOnly : <boolean>,>
<buildIndexes : <boolean>,>
<hidden : <boolean>,>
<priority: <priority>,>
<tags: { <document> },>
<slaveDelay : <number>,>
<votes : <number>>
}
, ...
],
<settings: {
<getLastEerrorDefaults : <lasterrdefaults>,>
<getLastErrorModes : <modes>>
}>
}
設定ドキュメントは、
オプション | 内容 |
---|---|
_id | 必須のオプションです。ノードのIDを指定します。0から始まり、 |
host | 必須のオプションです。ノードのホスト名とポート番号を指定します |
arbiterOnly | ノードがアービターであるかどうかを、 |
priority | 0~1000までの整数値で、 |
votes | プライマリ選出のための投票権の数を指定します。投票権の数のデフォルトは1です。特別な理由が無い限り、 |
hidden | ノードを隠蔽するかどうかを、 |
buildIndexes | インデックスを作成するかどうかを、 |
slaveDelay | プライマリに選出されないノード |
tags | 「キー:値」 |
オプション | 内容 |
---|---|
getLastErrorDefaults | getLastErrorコマンドのデフォルトの引数を指定します。 getLastErrorコマンドについては、 |
getLastErrorModes | tagsオプションと組み合わせて、 |
レプリケーションでの重要項目
本記事の最後として、
- レプリケーションの仕組み
(Oplog) - 書き込み保証
- 読み取りスケーリング
- 書き込み/読み取りのタグ付けによる制御
レプリケーションの仕組み(Oplog)
Oplogはレプリケーションに必要なデータ操作オペレーションの集合です
Oplogに保存されているもの
前ページで構築したレプリケーション環境を使って、
$ mongo --port=30000 MongoDB shell version: 2.2.2 connecting to: 127.0.0.1:30000/test > use mydb switched to db mydb > db.logs.insert({x:1,y:1}) > use local switched to db local > db.oplog.rs.find().toArray() [ { "ts" : { "t" : 1357790856000, "i" : 1 }, "h" : NumberLong(0), "op" : "n", "ns" : "", "o" : { "msg" : "initiating set" } }, { "ts" : { "t" : 1357791170000, "i" : 1 }, "h" : NumberLong("6904647020561039377"), "op" : "i", "ns" : "mydb.logs", "o" : { "_id" : ObjectId("50ee3fc023dff231bac25b95"), "x" : 1, "y" : 1 } } ]
2つのドキュメントが表示されますが、
フィールド名 | 保存されているデータ |
---|---|
ts | オペレーションのBSONタイムスタンプ |
h | オペレーションのユニークなID |
op | オペレーションの種類。"i" insert,"u" update,"d" delete,"c" dbコマンド,"n" オペレーション無しなどがある |
ns | ネームスペース。データベース.コレクションで表記される。 |
o | オペレーションで使用されたドキュメントのコピー。 |
Stale問題
プライマリ/セカンダリ間でOplogの同期が追いつかない場合、
replSet error RS102 too stale to catch up
停止してしまったレプリケーションに対する解決策は、
Oplogのサイジング
OplogはCapped Collectionなので、
システム | デフォルトサイズ |
---|---|
32bit | 50MB |
64bit | 1GBまたは、 |
Mac OS X | 192MB |
Staleを避けるために、
Oplogの適切なサイズを見積もる方法のひとつに、
> db.getReplicationInfo()
1時間で作成されたOplogのサイズがわかれば、
書き込み保証
クエリにオプションを指定することによって、

オプション | 書き込み保証レベル |
---|---|
デフォルト | 無し |
w = 1 | メモリ |
w = 2 | 最低でも1つのサーバにレプリケーション |
j:true | ジャーナル |
w = majority | レプリケーションに参加しているノードの過半数 |
w = "タグ" | タグで指定したノード |
たとえば、
@collection.insert(doc, :safe => {:w => 2, j: => true})
読み取り負荷分散
MongoDBのレプリケーションでは、
設定名 | 意味 |
---|---|
PRIMARY | PRIMARYノードのみからReadする |
PRIMARY PREFERRED | 可能であればPRIMARYノードからReadする |
SECONDARY | SECONDARYノードのみからReadする |
SECONDARY PREFERRED | 可能であればSECONDARYノードからReadする |
NEAREST | レイテンシの小さいノードからReadする |
必ず最新のデータが必要な場合には、

Rubyでの設定例は以下のようになります。
@collection.find({:doc => 'foo'}, :read => :primary) #primaryノードからReadする @collection.find({:doc => 'foo'}, :read => :secondary) #secondaryノード群からReadする
書き込み/読み取りのタグ付けによる制御
tagsオプションとgetLastErrorModesを組み合わせることで、
> config = { _id : "multidcrep", members : [ { _id : 0, host : "yokohama1.example.com", tags : { dc : "Yokohama" } }, { _id : 1, host : "yokohama2.example.com", tags : { dc : "Yokohama" } }, { _id : 2, host : "yokohama3.example.com", tags : { dc : "Yokohama" } }, { _id : 3, host : "tokyo1.example.com", tags : { dc : "Tokyo" } }, { _id : 4, host : "osaka1.example.com", tags : { dc : "Osaka" } } ], settings : { getLastErrorModes : { multiDC : { dc : 2 } } } }
※ "dc : 2"は、
定義したモード"multiDC"を利用するには、
@collection.insert(doc, :safe => {:w => "multiDC"})
次回のテーマ
今回はMongoDBが備えているレプリケーション機能について説明しました。MongoDBはスケールアウトを前提に設計されているので、
次回はMongoDBのもうひとつのスケールアウトの仕組みであるシャーディングについて説明します。