はじめに
今回は、
最初にレプリケーションの概要を説明します。次に実際に構築する手順を説明した後、
前回の記事の最後に、
レプリケーションのメリット
まずは、
可用性の向上
レプリケーションは主に冗長性を得るために設計され、
保守性の向上
レプリケーションは負荷の高い操作をスレーブノードで実行できることにより、
負荷分散による性能の向上
レプリケーションを使用すれば、
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のもうひとつのスケールアウトの仕組みであるシャーディングについて説明します。


