日頃の行い

個人的な日頃の行いをつらつら書いてます\\\\ ٩( 'ω' )و ////

MongoDBのInitial Syncを試してみた。

みなさんこんばんは。
遂に同期とリレーブログを始めることにしました。
酒呑みたちのリレーブログ - はてなブログ グループ

酒呑みたちのリレーブログというわりとひどいタイトルにしたのに誰も文句言いいませんでした。
みんな自覚しているようです。

さて、今回なにしようかなと思ったんですが、仕事でMongoDBを使っているので、それ関連にしようと思いました。

とある理由により、MongoDBでよくInitial Syncをすることが多いんですが、
Tutorialをしっかり読んだことなかったので、それを読みつつ実際に行ってみようと思いました。

前提(MongoDBのレプリケーションについて)

前提として、MongoDBのレプリケーションはPrimaryのlocal データベースのoplogというコレクションに変更に関する情報を持たせて、それをSecondary達が読みに行き、同期を行うといった方法で行っています。
レプリケーションが済んだ状態で、以下の様なコマンドを実行すると見れるはずです。

$ mongo
MongoDB shell version: 2.6.5
connecting to: test
repltry:PRIMARY> use local
switched to db local
repltry:PRIMARY> db.oplog.rs.find()

MongoDBのtutorialを読んでみる。

ここから引用して、自分なりに訳してみました。
Resync a Member of a Replica Set — MongoDB Manual 2.6.4


引用

A replica set member becomes “stale” when its replication process falls so far behind that the primary overwrites oplog entries the member has not yet replicated.
The member cannot catch up and becomes “stale.”
When this occurs, you must completely resynchronize the member by removing its data and performing an initial sync.

  • レプリケーションが遅れ、レプリカセットのあるメンバーがまだ追いついていないoplogをprimary機が上書きしてしまうことでレプリケーションに失敗した際、そのあるレプリカセットは "stale" 状態になる。
  • そのメンバーは(primaryの変更を)キャッチアップ出来ず、"stale"状態となる。
  • それが起きてしまった際には、全データを削除しInitial Syncを実行することによる、完全な再同期が必要となる。

引用

MongoDB provides two options for performing an initial sync:

・Restart the mongod with an empty data directory and let MongoDB’s normal initial syncing feature restore the data.
 This is the more simple option but may take longer to replace the data.
 See Procedures.
・Restart the machine with a copy of a recent data directory from another member in the replica set.
 This procedure can replace the data more quickly but requires more manual steps.
 See Sync by Copying Data Files from Another Member.

  • MongoDBのInitla Syncには2つの方法があります。

1. データを保存しているディレクトリを空の状態でmongodを再起動し、MongoDBによる通常のInitial Sync機能によってデータを復帰させます。
 これは他方より簡単ですが、少し時間がかかるでしょう。
2. レプリカセット内の他メンバーから、最新のデータディレクトリをコピーし、サーバを再起動します。
 これは他方より早いですが、手動のステップが必要になります。


...だそうです。

つまり、MongoDBのレプリケーションでは、Primary機の oplog はある程度書き込まれると、はじめから上書きで書きなおされてしまうため、それに追いつけなかったSecondary機は"stale"状態になるんですね。
そんな時にInitial Syncが必要になるということですね。

(staleってなんだろう・・・)
staleの意味 - 英和辞典 Weblio辞書


実践Initial Sync

2つの方法があるようでしたが、今回は楽めな方を試してみようと思います。
つまり、 Procedures のほうを試します。

MongoDBのレプリケーションは以前やったことがあるので、それを利用します。

よかったら記事も・・・w

MongoDBでレプリカセットを試してみた。 - 日頃の行い

利用したレポジトリはこちらです。
tarata/mongo-replication-try · GitHub

Install

$ vagrant up
[vagrant@localhost ~]$ vagrant ssh web1
[vagrant@localhost ~]$ sh /vagrant/script/master-setup.sh

で準備完了のはずですw

ここからの流れは

1. ダミーデータを突っ込む。
2. 一度同期させる。
3. SecondaryのInitial Sync

です。

ダミーデータを突っ込む。

ダミーデータどうしようかなと思ったらMongoDBのページに有りましたw

Generate Test Data — MongoDB Manual 2.6.4

こいつを元に作ったシェルスクリプトさんを呼び出します。
Primary機で

$ sh /vagrant/script/mongo-dummydata.sh

で多分100,000件くらい突っ込まれますw

一度同期させる

待ちましょうw
終わったかどうかは以下のような方法で確認できます。

$ mongo
$ rs.status()
repltry:PRIMARY> rs.status()
{
	"set" : "repltry",
	"date" : ISODate("2014-10-12T08:57:02Z"),
	"myState" : 1,
	"members" : [
		{
			"_id" : 0,
			"name" : "192.168.56.101:27017",
			"health" : 1,
			"state" : 1,
			"stateStr" : "PRIMARY",
			"uptime" : 8184,
			"optime" : Timestamp(1413096918, 284),  //ここと
			"optimeDate" : ISODate("2014-10-12T06:55:18Z"),
			"electionTime" : Timestamp(1413096243, 1),
			"electionDate" : ISODate("2014-10-12T06:44:03Z"),
			"self" : true
		},
		{
			"_id" : 1,
			"name" : "192.168.56.102:27017",
			"health" : 1,
			"state" : 2,
			"stateStr" : "SECONDARY",
			"uptime" : 7987,
			"optime" : Timestamp(1413096918, 284), // ここが一緒であれば終わってます。
			"optimeDate" : ISODate("2014-10-12T06:55:18Z"),
			"lastHeartbeat" : ISODate("2014-10-12T08:57:01Z"),
			"lastHeartbeatRecv" : ISODate("2014-10-12T08:57:00Z"),
			"pingMs" : 0,
			"syncingTo" : "192.168.56.101:27017"
		},
		{
			"_id" : 2,
			"name" : "192.168.56.103:27017",
			"health" : 1,
			"state" : 7,
			"stateStr" : "ARBITER",
			"uptime" : 7987,
			"lastHeartbeat" : ISODate("2014-10-12T08:57:01Z"),
			"lastHeartbeatRecv" : ISODate("2014-10-12T08:57:01Z"),
			"pingMs" : 0
		}
	],
	"ok" : 1
}

SecondaryのInitial Sync

Tutorialを見ると、以下の様なことを行えば良いようです。

a. Stop the member’s mongod instance. To ensure a clean shutdown, use the db.shutdownServer() method from the mongo shell or on Linux systems, the mongod --shutdown option.

b. Delete all data and sub-directories from the member’s data directory. By removing the data dbPath, MongoDB will perform a complete resync. Consider making a backup first.

1. 正規方法でMongoを止める。
2. データディレクトリを削除する。

そして起動すればいいんですね。
やってみましょう。

repltry:SECONDARY> use admin
switched to db admin
repltry:SECONDARY> db.shutdownServer()
2014-10-12T09:20:18.817+0000 DBClientCursor::init call() failed
server should be down...
2014-10-12T09:20:18.818+0000 trying reconnect to 127.0.0.1:27017 (127.0.0.1) failed
2014-10-12T09:20:18.819+0000 warning: Failed to connect to 127.0.0.1:27017, reason: errno:111 Connection refused
2014-10-12T09:20:18.819+0000 reconnect 127.0.0.1:27017 (127.0.0.1) failed failed couldn't connect to server 127.0.0.1:27017 (127.0.0.1), connection attempt failed
2014-10-12T09:20:18.836+0000 trying reconnect to 127.0.0.1:27017 (127.0.0.1) failed
2014-10-12T09:20:18.839+0000 warning: Failed to connect to 127.0.0.1:27017, reason: errno:111 Connection refused
2014-10-12T09:20:18.839+0000 reconnect 127.0.0.1:27017 (127.0.0.1) failed failed couldn't connect to server 127.0.0.1:27017 (127.0.0.1), connection attempt failed

# そのころPrimaryでは...

repltry:PRIMARY> rs.status()
{
	"set" : "repltry",
	"date" : ISODate("2014-10-12T09:22:59Z"),
	"myState" : 1,
	"members" : [
		{
			"_id" : 0,
			"name" : "192.168.56.101:27017",
			"health" : 1,
			"state" : 1,
			"stateStr" : "PRIMARY",
			"uptime" : 9741,
			"optime" : Timestamp(1413096918, 284),
			"optimeDate" : ISODate("2014-10-12T06:55:18Z"),
			"electionTime" : Timestamp(1413096243, 1),
			"electionDate" : ISODate("2014-10-12T06:44:03Z"),
			"self" : true
		},
		{
			"_id" : 1,
			"name" : "192.168.56.102:27017",
			"health" : 0,
			"state" : 8,
			"stateStr" : "(not reachable/healthy)",
			"uptime" : 0,
			"optime" : Timestamp(1413096918, 284),
			"optimeDate" : ISODate("2014-10-12T06:55:18Z"),
			"lastHeartbeat" : ISODate("2014-10-12T09:22:59Z"),
			"lastHeartbeatRecv" : ISODate("2014-10-12T09:20:18Z"),
			"pingMs" : 0,
			"syncingTo" : "192.168.56.101:27017"
		},
		{
			"_id" : 2,
			"name" : "192.168.56.103:27017",
			"health" : 1,
			"state" : 7,
			"stateStr" : "ARBITER",
			"uptime" : 9544,
			"lastHeartbeat" : ISODate("2014-10-12T09:22:59Z"),
			"lastHeartbeatRecv" : ISODate("2014-10-12T09:22:59Z"),
			"pingMs" : 0
		}
	],
	"ok" : 1
}

ちゃんと死んでますね。
ではデータを消しましょう。
データの場所は /etc/mongod.conf のdbpathに記述されています。

$cat /etc/mongod.conf
logpath=/var/log/mongodb/mongod.log

logappend=true

fork=true

dbpath=/var/lib/mongo

pidfilepath=/var/run/mongodb/mongod.pid
$ sudo rm -rf * /var/lib/mongo/*

これでおkかと。

では起動しましょう。

$ sudo service mongod start
$ mongo
repltry:STARTUP2>

STARTUP2となっていますね。
これでinitial syncが始まります。
Primaryの方で見てみると

repltry:PRIMARY> rs.status()
{
	"set" : "repltry",
	"date" : ISODate("2014-10-12T09:33:22Z"),
	"myState" : 1,
	"members" : [
		{
			"_id" : 0,
			"name" : "192.168.56.101:27017",
			"health" : 1,
			"state" : 1,
			"stateStr" : "PRIMARY",
			"uptime" : 10364,
			"optime" : Timestamp(1413096918, 284),
			"optimeDate" : ISODate("2014-10-12T06:55:18Z"),
			"electionTime" : Timestamp(1413096243, 1),
			"electionDate" : ISODate("2014-10-12T06:44:03Z"),
			"self" : true
		},
		{
			"_id" : 1,
			"name" : "192.168.56.102:27017",
			"health" : 1,
			"state" : 5,
			"stateStr" : "STARTUP2",
			"uptime" : 16,
			"optime" : Timestamp(0, 0),
			"optimeDate" : ISODate("1970-01-01T00:00:00Z"),
			"lastHeartbeat" : ISODate("2014-10-12T09:33:20Z"),
			"lastHeartbeatRecv" : ISODate("2014-10-12T09:33:21Z"),
			"pingMs" : 12,
			"lastHeartbeatMessage" : "initial sync need a member to be primary or secondary to do our initial sync"
		},
		{
			"_id" : 2,
			"name" : "192.168.56.103:27017",
			"health" : 1,
			"state" : 7,
			"stateStr" : "ARBITER",
			"uptime" : 10167,
			"lastHeartbeat" : ISODate("2014-10-12T09:33:21Z"),
			"lastHeartbeatRecv" : ISODate("2014-10-12T09:33:21Z"),
			"pingMs" : 0
		}
	],
	"ok" : 1
}
# 数秒後...
repltry:PRIMARY> rs.status()
{
	"set" : "repltry",
	"date" : ISODate("2014-10-12T09:34:01Z"),
	"myState" : 1,
	"members" : [
		{
			"_id" : 0,
			"name" : "192.168.56.101:27017",
			"health" : 1,
			"state" : 1,
			"stateStr" : "PRIMARY",
			"uptime" : 10403,
			"optime" : Timestamp(1413096918, 284),
			"optimeDate" : ISODate("2014-10-12T06:55:18Z"),
			"electionTime" : Timestamp(1413096243, 1),
			"electionDate" : ISODate("2014-10-12T06:44:03Z"),
			"self" : true
		},
		{
			"_id" : 1,
			"name" : "192.168.56.102:27017",
			"health" : 1,
			"state" : 2,
			"stateStr" : "SECONDARY",
			"uptime" : 55,
			"optime" : Timestamp(1413096918, 284),
			"optimeDate" : ISODate("2014-10-12T06:55:18Z"),
			"lastHeartbeat" : ISODate("2014-10-12T09:34:00Z"),
			"lastHeartbeatRecv" : ISODate("2014-10-12T09:34:01Z"),
			"pingMs" : 0,
			"syncingTo" : "192.168.56.101:27017"
		},
		{
			"_id" : 2,
			"name" : "192.168.56.103:27017",
			"health" : 1,
			"state" : 7,
			"stateStr" : "ARBITER",
			"uptime" : 10206,
			"lastHeartbeat" : ISODate("2014-10-12T09:33:59Z"),
			"lastHeartbeatRecv" : ISODate("2014-10-12T09:33:59Z"),
			"pingMs" : 1
		}
	],
	"ok" : 1
}

のようになっていました。
結構さくっと終わっちゃったので、早めにrs.status()を実行しないと見れません。
こんな感じでInitial Syncが出来るようです。
また色々と試してみようかなと思います。