MongoDBでゆるふわDB体験

第4回 MongoDBのレプリケーションを構築してみよう

この記事を読むのに必要な時間:およそ 6.5 分

レプリケーションを試してみよう

前のページで説明したように,MongoDBでレプリカセットを構成するには,最低3つのノードを含める必要があります。今回は,アービターを含めた3ノード構成で1台のPC上でレプリカセットを構築します。

それでは,レプリカセットをセットアップしましょう。まず,各ノードのデータディレクトリを作成します。

$ mkdir /data/node1
$ mkdir /data/node2
$ mkdir /data/node3

次に,各ノードのmongodを起動します。今回は同じマシン上で起動しましょう。

$ 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

「--fork」オプションを付けると,mongodをバックグラウンドで実行できます。その際は,ログの出力先を指定する「--logpath」オプションが必要です。また,⁠--logappend」オプションを付けるとログの追記が可能です。

これらのノードは全て同じレプリカセットに所属させたいので,replSetには同じ値を指定します。これだけではレプリカセットを構成することはできず,さらに設定を加える必要があります。まず,いずれか1つのノードに接続します。

$ mongo --port=30000
MongoDB shell version: 2.2.2
connecting to: 127.0.0.1:30000/test

JSON形式でレプリカセットの設定を変数に格納し,rs.initiate()コマンドの引数として渡すことで,レプリカセットの初期化を行います。今後,JSON形式のレプリカセットの設定を「設定ドキュメント」と呼びます。今回は,設定ドキュメントをconfigという変数に格納します。

> 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アドレス]は,ループバックインターフェース(localhostや127.0.0.1)とそれ以外の混在はできませんので,注意してください。

rs.addコマンドを使う方法もあります。

30002番ポートのノードは,アービターとして起動するように「arbiterOnly : true」を設定しています。設定についての詳細は,次のページで解説しています。

現在のレプリカセットの状態を,rs.status()コマンドで見てみましょう。

> 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インターフェース(http://localhost:[各ノードのポート番号+1000]/)からも確認できます。

[参考]
rs.status()コマンドで確認できる,レプリカセットの各ノードが取りうる状態(stateStr)表1の通りです。

表1 ノードが取りうる状態

表記状態
STARTUPノード間で設定を共有し,調整中であることを示します。
PRIMARYプライマリであることを示します。
SECONDARYセカンダリであることを示します。priorityが0より大きく,かつhidden指定が無い場合は,フェイルオーバーの際にプライマリになる可能性があります。
RECOVERINGフェイルオーバーや新しいノードの追加等により,一時的に読み書きができないことを示します。
FATALネットワークは接続されているものの,ping応答が無いことを示します。
STARTUP2データファイルの同期が進行中であることを示します。
UNKNOWNネットワークが接続されていないことを示します。
ARBITERアービターであることを示します。
DOWNアクセス可能であるものの,ping応答が無いことを示します。
ROLLBACKロールバック処理中であることを示します。

各ノードのstateStrが"PRIMARY","SECONDARY","ARBITER"となっていることが確認できました。

以上の操作でレプリカセットが構築できました。

動作確認

レプリケーションの確認

作成したレプリカセットにデータを挿入し,実際にレプリケーションできるかどうかを確認します。まずは,プライマリであるポート番号30000のノードに,ドキュメントを10000件挿入してみます。

$ 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<10000; i++) db.logs.insert(
    { "uid":i, "value":Math.floor(Math.random()*10000+1) } )

> db.logs.count()
10000

挿入したドキュメントがレプリケーションされているかどうか,セカンダリであるポート番号30001のノードから確認します。

$ 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 }

このようなエラーが出力されました。プライマリ以外のノードからの読み込みを許可するために,setSlaveOk()コマンドを利用する必要があります。

> db.getMongo().setSlaveOk()
> db.logs.count()
10000

セカンダリにも,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"へと切り替わり,フェイルオーバーに成功しました。

リカバリの確認

リカバリは,フェイルオーバーの際にkillしたノードを立ち上げるだけで可能です。

$ 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番ポートのノードがセカンダリとしてリカバリしたことが確認できました。

次ページでは,レプリケーションのオプションについて解説します。

著者プロフィール

藤崎祥見(ふじさきしょうけん)

野村総合研究所 OpenStandia所属。オープンソースのR&Dとセミナー講師を担当。

Debian,Ubuntu,Liferayのコミュニティで活動した後,MongoDBの翻訳に関わり,丸の内MongoDB勉強会を始める。

実家がお寺で,住職の資格を所持する坊主系エンジニア。

Twitter:@syokenz


渡部徹太郎(わたなべてつたろう)

野村総合研究所 OpenStandia所属。オープンソースを使ったSIやサポートの業務に従事。

藤崎と共同で丸の内MongoDB勉強会を始める。

趣味は自宅サーバ。好きなものはLinuxとRuby。

Twitter:@fetarodc


林田敦(はやしだあつし)

野村総合研究所 OpenStandia所属。オープンソースを使ったSIや製品開発業務に従事。

丸の内MongoDB勉強会では広報兼雑用係を務める。

趣味はレザークラフト,ダイビング,スキー,キャンプ,ジェットスキー,カメラ等々。作って滑って撮って潜れるエンジニア。

Facebook:Atsushi Hayashida