読者です 読者をやめる 読者になる 読者になる

猫も杓子も記事を書く

140文字ではかけないことをかこうと思います。

【解決した!!!】MongoDBのざっくりとした解説と謎

久々にプログラマっぽいことを書いてみる。

↓ここからMongoDBがわからない人向けのお話パート

今、仕事でMongoDBを触っています。
なんぞや?って方に説明しますと、データベースの一種です。ざっくりいうと、今までのデータベースとくらべてデータの参照・追加・更新・削除をよりちょっぱやにできるようにというコンセプトで作られたもので、MySQLといった従来のデータベースとは仕組みが違います。興味のある方は調べてみてください。

んで、MongoDBの機能の一つに、レプリカセットというものがあります。
これもググってもらったほうが理解が早まる感が否めませんが、複数のMongoDBでひとかたまりとしてデータを共有することで負荷*1をそれぞれのデータベースで分散できるので高負荷に強くなり、万が一、ある1台のデータベースがダウンしても、生きている他のデータベースを代わりに働かせるように設定できる機能があり*2、それによってグループ化されたデータベース群のことをいいます。
勿論他のデータベースにもレプリケーション機能はありますが、MongoDBはとにかく膨大な量のデータを扱うケースが多いため、完全ダウンしないようにデータベースを設計することが重要であり、ですからぼくのようなズブの素人でも簡単にレプリカセットを構築できるように作られているのです。

このレプリカセット、主従関係があります。
マスタースレーブ構成といって、名前のまんまですが、レプリカセットの中で、メインとサブのデータベースを決めてるんですね。メインのデータベースをMongoDBではprimary、サブのデータベースをMongoDBではsecondaryと呼称します。
primaryは1つのレプリカセットに1台だけ存在できる、いわばレプリカセットの中の頭領です。一方のsecondaryは子分みたいなもんなので、構築するレプリカセットに応じて何台でも組み入れることができます。
基本的にデータの追加・更新・削除はprimaryが一人で担い、参照アクセスに対してはprimaryとsecondaryが手分けして対応するイメージです。仲間だもんげ!
この2種類に加えて、Arbiter(アービター)というポジションがあります。データの読み書きには一切参加しませんが、「決定者・仲裁人」という名前の如く、もしprimaryがダウンしたとき、secondaryをprimaryに昇格させる役割を持っています。ただこいつはレプリカセットには必須ではなく、あって損なし、くらいのポジションです。Arbiterがいなかったら、secondaryの中から一人が合議制によって選ばれます。*3
MongoDBのレプリカセットにおいては、とにかくprimaryを存続させることが至上命題です。primaryが落ちると、データの参照以外一切できなくなってしまいますから・・・
なので、primaryがダウンしたら、secondaryのうちの誰かが代わりにprimaryになります。これがレプリカセットにおける大原則です。

↓ここから謎パート

以上を踏まえて、ようやく謎について書きます。
今仕事で触っているMongoDBのレプリカセットは、primary、secondary、Arbiterが各1台ずつ、合計3台によって構成されています。以下のような感じ。

rs.status()
{
        "set" : "testReplicaSet",
        "date" : ISODate("2016-11-29T01:07:44Z"),
        "myState" : 2,
        "syncingTo" : "163.44.166.156:27017",
        "members" : [
                {
                        "_id" : 0,
                        "name" : "xx.xx.xxx.xxx:27017",
                        "health" : 1,
                        "state" : 7,
                        "stateStr" : "ARBITER",
                        "uptime" : 14,
                        "lastHeartbeat" : ISODate("2016-11-29T01:07:43Z"),
                        "lastHeartbeatRecv" : ISODate("2016-11-29T01:07:42Z"),
                        "pingMs" : 3
                },
                {
                        "_id" : 1,
                        "name" : "yy.yyy.yy.yyy:27017",
                        "health" : 1,
                        "state" : 2,
                        "stateStr" : "SECONDARY",
                        "uptime" : 66420,
                        "optime" : Timestamp(1480315218, 1),
                        "optimeDate" : ISODate("2016-11-28T06:40:18Z"),
                        "infoMessage" : "syncing to: 163.44.166.156:27017",
                        "self" : true
                },
                {
                        "_id" : 2,
                        "name" : "zzz.zz.zzz.zzz:27017",
                        "health" : 1,
                        "state" : 1,
                        "stateStr" : "PRIMARY",
                        "uptime" : 91,
                        "optime" : Timestamp(1480315218, 1),
                        "optimeDate" : ISODate("2016-11-28T06:40:18Z"),
                        "lastHeartbeat" : ISODate("2016-11-29T01:07:43Z"),
                        "lastHeartbeatRecv" : ISODate("2016-11-29T01:07:43Z"),
                        "pingMs" : 0,
                        "electionTime" : Timestamp(1480381583, 1),
                        "electionDate" : ISODate("2016-11-29T01:06:23Z")
                }
        ],
        "ok" : 1
}

rs.status()というのが読んで字の如くなレプリカセットの状態を見られるコマンド
。 stateStrがそれぞれ、primary、secondary、Arbiterとなっているのがわかると思います。
ここから、secondaryとArbiterのMongoDBを停止*4し、primary一台だけにしてみます。
MongoDBを止める際は、Linuxサーバー上であればserviceで問題ありません。

[root@secondaryサーバー ~]# sudo service mongod stop
Stopping mongod:                                           [  OK  ]
[root@secondaryサーバー ~]# 
[root@Arbiterサーバー ~]# sudo service mongod stop
Stopping mongod:                                           [  OK  ]
[root@Arbiterサーバー ~]# 

止めた状態で、再度レプリカセットの状態を確認してみます。
MongoDBはprimaryが命。正直primaryさえあれば稼働が止まることはないですから、primaryだけ生き残ってくれさえすればそれでよいのだが・・・

rs.status()
{
        "set" : "testRepl",
        "date" : ISODate("2016-11-29T01:16:50Z"),
        "myState" : 2,
        "members" : [
                {
                        "_id" : 0,
                        "name" : "xx.xx.xxx.xxx:27017",
                        "health" : 0,
                        "state" : 8,
                        "stateStr" : "(not reachable/healthy)",
                        "uptime" : 0,
                        "lastHeartbeat" : ISODate("2016-11-29T01:16:49Z"),
                        "lastHeartbeatRecv" : ISODate("2016-11-29T01:15:18Z"),
                        "pingMs" : 0
                },
                {
                        "_id" : 1,
                        "name" : "yy.yyy.yy.yyy:27017",
                        "health" : 1,
                        "state" : 2,
                        "stateStr" : "(not reachable/healthy)",
                        "uptime" : 66966,
                        "optime" : Timestamp(1480315218, 1),
                        "optimeDate" : ISODate("2016-11-28T06:40:18Z"),
                        "self" : true
                },
                {
                        "_id" : 2,
                        "name" : "zzz.zz.zzz.zzz:27017",
                        "health" : 0,
                        "state" : 8,
                        "stateStr" : "SECONDARY",
                        "uptime" : 0,
                        "optime" : Timestamp(1480315218, 1),
                        "optimeDate" : ISODate("2016-11-28T06:40:18Z"),
                        "lastHeartbeat" : ISODate("2016-11-29T01:16:48Z"),
                        "lastHeartbeatRecv" : ISODate("2016-11-29T01:12:40Z"),
                        "pingMs" : 0
                }
        ],
        "ok" : 1
}

・・・どうしてこうなった。
primaryが何故かsecondaryに降格し、secondaryだけで稼働している状態になっているのがわかるでしょうか?
こうなると、secondaryを経由してのデータの参照以外、なにもできなくなってしまいます。
リスク回避等の目的でそういう「仕様」なのであれば、仕様なら仕方ないよね、としか言いようがないのですが、
今のところ、公式をあさっても、そういった記述はありません。
だから、事象として確認できるだけで、確たる裏付けがないのです。

困ったなあ。

参考

https://docs.mongodb.com/manual/replication/docs.mongodb.com

qiita.com

qiita.com

qiita.com

2017/03/05 追記

あれから数ヶ月たちました。
結局、社内での検討の結果、MongoDBは採用されず、
この件についても有耶無耶になったまま止まっていました。
このままにしておくのも気持ち悪かったので、改めて調べてみたら、何の事はない、ちゃんと公式に書いてありました。

Replica Set Elections — MongoDB Manual 3.4

If a majority of the replica set is inaccessible or unavailable to the current primary, the primary will step down and become a secondary. The replica set cannot accept writes after this occurs, but remaining members can continue to serve read queries if such queries are configured to run on secondaries.

レプリカセットの過半数以上がアクセス不可(or利用不可)の場合はprimaryはsecondaryに降格します。
この状態ではレプリカセットへのデータ書き込みは出来ませんが、secondaryが生存している限り、読み込みは続けることが出来ます。

・・・とのこと。
つまり、上記の例ではarbiterを含め、2台以上生き残っていなければいけなかった、ということですね。なるほど。
しょうもないオチではありましたが、ひとつスッキリする結論に達することが出来ました。
コメントいただいたkabao2003さん、ありがとうございました。

*1:ここでいう「負荷」とは、アクセス負荷をイメージしてもらえばいいと思います。

*2:この機能自体はレプリケーションとか、フォールトトレランスとか言ったりします。

*3:もしsecondaryが全員自分に投票したら当然票が割れるので、Arbiterを組み込むのはそれを防ぐ意味合いもあります。

*4:上記で言うと、上から1番目と2番目ですね。