MongoDBにおけるレプリケーションとシャーディング -構築編1- | よしやのブログ

よしやのブログ

ブログの説明を入力します。

まずはレプリケーションの構築について書きます。

ここで想定しているOSは
Mac OSX
CentOS6.4
です。
また、IPの異なるワーカーをいくつも用意するのは金銭上、メモリ上ちょっとあれだったので

全てローカルホスト上での構築を前提にします。(使うIPは localhost, つまり127.0.0.1だけ)

あらゆる数のサーバでやるのが本筋ですが、ここでの手順は容易に拡張できます。
ただし、CentOSのシェルで次の事を確認してください。

$ hostname
$ hostname -i

これでまずUnkwoun hostとか出ないように etc/hosts とかにホスト名を追加してください。
出ないと、レプリカ構築中に名前解決が出来ずに詰みます。(Secondly Primaryの振り分けが出来なくなる)

--------------------------------------------------------------------
レプリケーションの構築

まず、DBの保存先を作っておきます。

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

ターミナルを複数立ち上げて、次のコマンドを実行してください。

$ mongod --replSet myapp --dbpath /data/node1 --port 40000
$ mongod --replSet myapp --dbpath /data/node2 --port 40001
$ mongod --replSet myapp --dbpath /data/node3 --port 40002

ここで、各オプションを説明します。

--replSet : このmongodプロセスをレプリカセットとして立ち上げるという意味
myapp : レプリケーションを構築するときのクラスタIDです。これが同じもの同士でレプリカが作られる

--dbpath /data/node1 : データベースの保存先を記述。さっき書いた/data/node1とか。

--port : 起動するポート番号。ちゃんと空いているポートで、他が使ってないものを選択してください。例えば80はapacheに使われるからダメ。8080はtomcatが使うからダメ。

IPの異なるサーバが複数ある場合は、それぞれのサーバのターミナルからこれらのコマンドを入力する。

さて、ここで一応logを確認してみましょう。環境で違いますが、多分 /var/log/mongod.logとか
にあるのでは?

これを見ると、
[startReplSets] replSet can't get local.system.replset
config from self or any seed (EMPTYCONFIG)

とか書かれてます。
そう、設定ファイルが見つからないままレプリケションしてるのでエラーです。これを無くしてレプリカを正常に動作させるには、mongod.confに必要な設定を書くか、mongoシェルから設定するか、いずれかです。本番環境では普通mongod.confに書きますが、ここでは簡単のため、mongoシェルから直接入力します。

ローカルからmongoシェルを起動して次のオブジェクトリテラルを書きます
$ mongo --host localhost --port 40000
> var config = {
"id" : "myapp"
"members" : [
{
"id" : 0,
"host" : "localhost:40000"
},
{
"id" : 1,
"host" : "localhost:40001"
},
{
"id" : 2,
"host" : "localhost:40002",
"arbiterOnly" : true
}
]
};
続いて以下のコマンド
> rs.initiate(config);

エラーが出る場合、configのどっかがおかしいです。例えば、idがさっき書いたクラスタIDじゃない。とかmembersのスペルが違うとか

実はこれでレプリケーション終了です。

試しに、次のコマンドをうつ。

> rs.status();
すると結果にこんなのが出るはずです。

{
"set" : "myapp",
"data" : ISO("2011-08-22T22:22:22),
"myState" : 1,
"members" : [
{
"id" : 0,
"name : "localhost:40000",
"health" : 1,
"state" : 1,
"stateStr" : "PRIMARY",
"optime" : {
"t" : 12334566,
"i" : 1
},
"optimeDate" : ISO("2011-08-22T22:22:22"),
"selt" : true
},
{
ここに似たようなやつ
"stateStr" : "SECONDARY"
},
{
ここに似たようなやつ
"stateStr" : "ARBITER"
}
],
"ok" : 1
}

注目してほしいのはこのPRIMARY SECONDARY ARBITERの3つです。

PRIMARY SECONDARYはレプリカセットのデータを持つものをさしています。
PRIMARYが何らかの理由で死んだ場合、SECONDARYが勝手にPRIMARYに昇格することで
サーバストップせずにデータを提供します。

PRIMARYにシェルからデータをinsertしてみるとSECODARYにすぐさま反映されます。

これがレプリケーションの全てです。
----------------------------------------------------------------------

少し深い話に入っていきます。

Q.レプリケーションってつまりは内部でどうやってるの?
A. oplogとハートビートに依存しています。

oplogって?
MySQLのマスタースレーブ型のレプリケーションを組んだ人なら何となく分かると思いますが、あれは番号付きのバイナリログに実行されたSQL文が突っ込まれており、それと全く同じクエリをスレーブで実行することで、レプリケーションを実現します。
それに似てます。
Mongoではキャップ付きのコレクション(新しいものを優先で保存して古いドキュメントは順次消えるようなコレクション) でDBに起きたCRUD操作を全て記録しています。
これをSECONDARYは読む訳です。
ここで、タイムスタンプからSECONDARYが適応した最後の変更を追跡していく。保存形式はもちろんBSONです。(JSONみたいなやつ)

実際PRIMARYでoplogを読んでみましょう。
すると
> use local
> db.oplog.$main.find().pretty();

ズラーと投げられたクエリ的なのが出てくると思います。

Q. あれ?ネットワーク分断された後に、プライマリとかセカンダリが更新されたらデータがごちゃごちゃになるんじゃ

A.マスタースレーブではなったらしいが、レプリカセットではなりません。なぜなら以下のアルゴリズムでセカンダリは自分自身のデータをあるべき姿に追従させるからです。

1 CRUDがPRIMARYに走る前に...
2 PRIMARYがoplogを書き換える
3 SECONDARYがPRIMARYのoplogをコピーしてくる
4 PRIMARYのoplogとSECONADARY自身のoplogのタイムスタンプを比べる
5 足りない分を保管するクエリを自分に流す
6 ほら整合性が保たれる

これがレプリケーションの正体なわけです。

Q oplogってcaped?じゃあ遅すぎる復帰はログが追えなくなって,つまり、ログが無くてレプリカセットに参加できないってこと?

A YES です。それはもう壊れています。ので、再びPRIMARYを同期するしかありません。相当時間かかりますが、
ですが、楽したいのであれば、oplogのサイズを増やすこともできます。
$ mongod --replSet myapp --oplogSize 1024
これは1024MBつまり、1GBです。このサイズは、各サービスで計算するしかありません。
人気サービスならそれだけCRUDが走るので8時間PRIMARYが落ちたときにoplogが終えなくなっているかもしれません。

Qなるほど、PRIMARYと整合性をとるのがoplogなんだね、でも勝手にPRIMARYとかSECONDARYとかに昇格したり降格する仕組みはこれじゃできなくね?

A YES そのためにARBITERがいるのです。

ARBITER PRIMARY SECONDARYはともにpingを送り合ってどれが生きてて、どこが死んでいるかを確認しています。
そんで、クラスタがネットワーク分断された場合、ARBITERは過半数以上残っていればレプリカセットはARBITERにより、上手い事、どっかが昇格、降格します。
しかし、ある日突然、全体の半分以上のノードが壊れた場合、
つまり、PRIMARYがSECONDARYやARBITERとpingで通信できなくなってしまった場合、これはPRIMARYから見ると過半数以下です。
すると...

勝手に自分が降格しますwww

すると分断で切り離されたところにPRIMARYがいなくなりますが、後の復帰によりPRIMARYが二つになるよりはましだという考えに基づいてます。それにARBITERが通信してる片方のSECONDARYがすぐにPRIMARYに昇格するので問題はありません。

Q 構築できたけど、これってどうやって使うの?プログラムからだと、普通にIPとか打つからその時接続したサーバ死んだら結局アプリ止まるから意味ないんだけど

A 各種ドライバには"レプリカセットでPRIMARYになってるどれかに接続する"みたいなことができます。例えばRubyでは

Mongo::ReplSetConnection.new(['localhost', 40000],['localhost', 400001],['localhost', 40002])
とか

Node.jsのMongoskinとかのラッパーなら

var db = mongoskin.db([
'192.168.0.1:27017/?auto_reconnect=true',
'192.168.0.2:27017/?auto_reconnect=true',
'192.168.0.3:27017/?auto_reconnect=true'
], {
database: 'testdb',
safe: true
}, {
connectArbiter: false,
socketOptions: {
timeout: 2000
}
});

こんな感じ。

便利な世の中です。