Replica Setとは、MongoDBでのレプリケーションの仕組みです。実際に設定を実施しながら、MongoDBでのレプリケーションの仕組みを確認します。
環境
- CentOS 8.1.1911
- MongoDB Community Edition v4.2.7
構成
各MongoDBサーバーの構成です。
MongoDB | IPアドレス |
---|---|
Primary | 192.168.0.10 |
Secondary | 192.168.0.20 |
Arbiter | 192.168.0.30 |
MongoDBのReplica Setは、3台以上の奇数にて構成する必要があり、PrimaryとSecondary、(必要あらば)Arbiterがメンバーとなります。PrimaryはMasterサーバー、SecondaryはReplicaサーバーです。Arbiterとは、
An arbiter does not have a copy of data set and cannot become a primary. However, an arbiter participates in elections for primary. An arbiter has exactly 1 election vote.
データは持たないけど、レプリケーション環境のPrimary選出の投票権(Replica Set Elections)はもつ、というやつです。
Replica Setの作成
Replica Set環境を作成します。各MongoDBサーバーは、既にMongoDBインストール済として作業を進めます。公式tutorialを参考に作業していきます。
コンフィグファイルの編集
/etc/mongod.conf
ファイルを編集して、 bindIp
パラメーターと、 replication
セクションに設定をします。PrimaryとSeecondaryサーバー共に、同じコンフィグ設定にした後、mongodプロセスを再起動しておきます。
... # network interfaces net: port: 27017 bindIp: 0.0.0.0 # Enter 0.0.0.0,:: to bind to all IPv4 and IPv6 addresses or, alternatively, use the net.bindIpAll setting. ... # replication Options replication: oplogSizeMB: 30 replSetName: "rs0" enableMajorityReadConcern: false ...
Replicationセクションについては、マニュアルの記述を参考に、調べた内容をまとめておきます。
補足:oplogについて
oplogSizeMB
パラメーターは、oplogサイズの指定をしています。oplogとは、
The oplog (operations log) is a special capped collection that keeps a rolling record of all operations that modify the data stored in your databases.
MongoDB applies database operations on the primary and then records the operations on the primary’s oplog. The secondary members then copy and apply these operations in an asynchronous process. All replica set members contain a copy of the oplog, in the local.oplog.rs collection, which allows them to maintain the current state of the database.
RDBのアクティブログ的なものをイメージしています。それをlocal.oplog.rsというcapped collectionに格納してくれています。capped collectionとは、
Capped collections are fixed-size collections that support high-throughput operations that insert and retrieve documents based on insertion order. Capped collections work in a way similar to circular buffers: once a collection fills its allocated space, it makes room for new documents by overwriting the oldest documents in the collection.
固定サイズの代わりに高速で動作してくれる、上書きローテーションされるコレクションということでしょうか。
oplogSizeMBパラメーターを設定する際に注意しないといけないことは、上書きされていくという事ですね。Secondary側のMongoDBが停止している間に、oplog内のデータが上書きされてしまった場合、レプリケーションされるべきデータがなくなってしまうので、Secondary側が復旧した後に同期できなくなってしまう。この点は、下記Qiitaの投稿が参考になりました。
補足:Read Concernについて
enableMajorityReadConcern
パラメーターは、defaultのRead Concern LevelをMajorityにすべきか定義しています。Read Concernとは、
The readConcern option allows you to control the consistency and isolation properties of the data read from replica sets and replica set shards.
MongoDB分散環境下での、読み取り一貫性を保証してくれるものですね。例えばfindクエリを実行する時に、以下のように指定して利用します。
> db.zips.find( ... { city: /NEW YORK/ } ... ).readConcern("majority") { "_id" : "10001", "city" : "NEW YORK", "loc" : [ -73.996705, 40.74838 ], "pop" : 18913, "state" : "NY" } { "_id" : "10002", "city" : "NEW YORK", "loc" : [ -73.987681, 40.715231 ], "pop" : 84143, "state" : "NY" }
Majorityレベルの動作は下記となります。多数のMemberが承認したデータを返す、とのこと。
The query returns the data that has been acknowledged by a majority of the replica set members. The documents returned by the read operation are durable, even in the event of failure.
その他のレベルについてこちら。
基本的にMajorityを利用すべきとのことですが、今回の構成(Primary, Secondary, Arbiterがそれぞれ1台構成)の場合、避けるべきとのことでパラメーター値をfalseにしています。
In general, avoid disabling "majority" read concern unless necessary. However, if you have a three-member replica set with a primary-secondary-arbiter (PSA) architecture or a sharded cluster with a three-member PSA shard, disable to prevent the storage cache pressure from immobilizing the deployment.
Replica Setのimit処理
コマンドを実行して、初期化処理を実施します。
PrimaryとしたいMongoDBに接続して、以下コマンドを実行します。
> rs.initiate( { ... _id: "rs0", ... members: [ ... { _id: 0, host: "192.168.0.10:27017" }, ... { _id: 1, host: "192.168.0.20:27017" } ... ] ... } ) { "ok" : 1, "$clusterTime" : { "clusterTime" : Timestamp(1592259434, 1), "signature" : { "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="), "keyId" : NumberLong(0) } }, "operationTime" : Timestamp(1592259434, 1) }
接続しているMongoDBサーバーがPrimaryとなります。以下コマンドにて設定情報の確認ができます。
rs0:PRIMARY> rs.conf() { "_id" : "rs0", "version" : 1, "protocolVersion" : NumberLong(1), "writeConcernMajorityJournalDefault" : true, "members" : [ { "_id" : 0, "host" : "192.168.0.10:27017", "arbiterOnly" : false, "buildIndexes" : true, "hidden" : false, "priority" : 1, "tags" : { }, "slaveDelay" : NumberLong(0), "votes" : 1 }, { "_id" : 1, "host" : "192.168.0.20:27017", "arbiterOnly" : false, "buildIndexes" : true, "hidden" : false, "priority" : 1, "tags" : { }, "slaveDelay" : NumberLong(0), "votes" : 1 } ], "settings" : { "chainingAllowed" : true, "heartbeatIntervalMillis" : 2000, "heartbeatTimeoutSecs" : 10, "electionTimeoutMillis" : 10000, "catchUpTimeoutMillis" : -1, "catchUpTakeoverDelayMillis" : 30000, "getLastErrorModes" : { }, "getLastErrorDefaults" : { "w" : 1, "wtimeout" : 0 }, "replicaSetId" : ObjectId("5ee7f36a3bd8152beb3b7f36") } }
ステータスの確認。
rs0:PRIMARY> rs.status() { "set" : "rs0", "date" : ISODate("2020-06-15T22:19:12.527Z"), "myState" : 1, "term" : NumberLong(1), "syncingTo" : "", "syncSourceHost" : "", "syncSourceId" : -1, "heartbeatIntervalMillis" : NumberLong(2000), "majorityVoteCount" : 2, "writeMajorityCount" : 2, "optimes" : { "lastCommittedOpTime" : { "ts" : Timestamp(1592259545, 1), "t" : NumberLong(1) }, "lastCommittedWallTime" : ISODate("2020-06-15T22:19:05.957Z"), "readConcernMajorityOpTime" : { "ts" : Timestamp(1592259545, 1), "t" : NumberLong(1) }, "readConcernMajorityWallTime" : ISODate("2020-06-15T22:19:05.957Z"), "appliedOpTime" : { "ts" : Timestamp(1592259545, 1), "t" : NumberLong(1) }, "durableOpTime" : { "ts" : Timestamp(1592259545, 1), "t" : NumberLong(1) }, "lastAppliedWallTime" : ISODate("2020-06-15T22:19:05.957Z"), "lastDurableWallTime" : ISODate("2020-06-15T22:19:05.957Z") }, "lastStableRecoveryTimestamp" : Timestamp(1592259505, 1), "lastStableCheckpointTimestamp" : Timestamp(1592259505, 1), "electionCandidateMetrics" : { "lastElectionReason" : "electionTimeout", "lastElectionDate" : ISODate("2020-06-15T22:17:25.879Z"), "electionTerm" : NumberLong(1), "lastCommittedOpTimeAtElection" : { "ts" : Timestamp(0, 0), "t" : NumberLong(-1) }, "lastSeenOpTimeAtElection" : { "ts" : Timestamp(1592259434, 1), "t" : NumberLong(-1) }, "numVotesNeeded" : 2, "priorityAtElection" : 1, "electionTimeoutMillis" : NumberLong(10000), "numCatchUpOps" : NumberLong(0), "newTermStartDate" : ISODate("2020-06-15T22:17:25.931Z"), "wMajorityWriteAvailabilityDate" : ISODate("2020-06-15T22:17:26.797Z") }, "members" : [ { "_id" : 0, "name" : "192.168.0.10:27017", "health" : 1, "state" : 1, "stateStr" : "PRIMARY", "uptime" : 238, "optime" : { "ts" : Timestamp(1592259545, 1), "t" : NumberLong(1) }, "optimeDate" : ISODate("2020-06-15T22:19:05Z"), "syncingTo" : "", "syncSourceHost" : "", "syncSourceId" : -1, "infoMessage" : "could not find member to sync from", "electionTime" : Timestamp(1592259445, 1), "electionDate" : ISODate("2020-06-15T22:17:25Z"), "configVersion" : 1, "self" : true, "lastHeartbeatMessage" : "" }, { "_id" : 1, "name" : "192.168.0.20:27017", "health" : 1, "state" : 2, "stateStr" : "SECONDARY", "uptime" : 117, "optime" : { "ts" : Timestamp(1592259545, 1), "t" : NumberLong(1) }, "optimeDurable" : { "ts" : Timestamp(1592259545, 1), "t" : NumberLong(1) }, "optimeDate" : ISODate("2020-06-15T22:19:05Z"), "optimeDurableDate" : ISODate("2020-06-15T22:19:05Z"), "lastHeartbeat" : ISODate("2020-06-15T22:19:12.384Z"), "lastHeartbeatRecv" : ISODate("2020-06-15T22:19:11.337Z"), "pingMs" : NumberLong(7), "lastHeartbeatMessage" : "", "syncingTo" : "192.168.0.10:27017", "syncSourceHost" : "192.168.0.10:27017", "syncSourceId" : 0, "infoMessage" : "", "configVersion" : 1 } ], "ok" : 1, "$clusterTime" : { "clusterTime" : Timestamp(1592259545, 1), "signature" : { "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="), "keyId" : NumberLong(0) } }, "operationTime" : Timestamp(1592259545, 1) }
Arbiterの追加
作成したReplica SetにArbiterサーバーを追加します。これもマニュアルを用意してくれています。
Arbiter用のDB Pathを作成
ArbiterとしたいMongoDBサーバーにて、Arbiter用のDB領域として利用するディレクトリーを作成しておきます。
$ sudo mkdir /var/lib/mongo/arb $ sudo chown mongod:mongod /var/lib/mongo/arb $ sudo chmod 700 /var/lib/mongo/arb
コンフィグファイルの編集
ArbiterとしたいMongoDBサーバーの /etc/mongod.conf
ファイルを編集します。
# Where and how to store data. storage: dbPath: /var/lib/mongo/arb journal: enabled: true # engine: # wiredTiger: ... # network interfaces net: port: 27017 bindIp: 0.0.0.0 # Enter 0.0.0.0,:: to bind to all IPv4 and IPv6 addresses or, alternatively, use the net.bindIpAll setting. ... # replication Options replication: replSetName: "rs0"
コンフィグを編集した後、mongodプロセスを再起動しておきます。
Arbiter追加コマンドの実行
PrimaryサーバーのMongoDBに接続して、以下のコマンドを実行します。
rs0:PRIMARY> rs.addArb("192.168.0.30:27017") { "ok" : 1, "$clusterTime" : { "clusterTime" : Timestamp(1592262280, 1), "signature" : { "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="), "keyId" : NumberLong(0) } }, "operationTime" : Timestamp(1592262280, 1) }
Arbiterサーバーが追加されています。
rs0:PRIMARY> rs.status().members[2] { "_id" : 2, "name" : "192.168.0.30:27017", "health" : 1, "state" : 7, "stateStr" : "ARBITER", "uptime" : 3022, "lastHeartbeat" : ISODate("2020-06-15T23:55:00.796Z"), "lastHeartbeatRecv" : ISODate("2020-06-15T23:55:00.779Z"), "pingMs" : NumberLong(5), "lastHeartbeatMessage" : "", "syncingTo" : "", "syncSourceHost" : "", "syncSourceId" : -1, "infoMessage" : "", "configVersion" : 2 }
アクセスコントロールの設定
Replica Setの環境に、アクセスコントロールの設定を有効にする方法です。マニュアルはこちら。
Deploy Replica Set With Keyfile Authentication
rootユーザーの作成
PrimaryサーバーのMongoDBに接続して、rootユーザーを作成しておきます。
> use admin > db.createUser( ... { ... user: "root", ... pwd: "password", ... roles: [ { role: "root", db: "admin" } ] ... } ... )
keyFileの作成
分散環境下のMongoDBでは、作成された鍵ファイルを利用して内部通信をしているらしいです。 A key’s length must be between 6 and 1024 characters and may only contain characters in the base64 set.
とあるので、マニュアルを参考に必要となる鍵ファイルを作成します。
opensslコマンドにて、乱数を作成しています。
$ openssl rand -base64 756 | sudo tee /var/lib/mongo/keyFile $ sudo chown mongod:mongod /var/lib/mongo/keyFile $ sudo chmod 400 /var/lib/mongo/keyFile
作成した鍵ファイルを、Replica Set環境の全MongoDBサーバーに配置します。
コンフィグファイルの編集
コンフィグファイルを編集して、作成したkeyFileを利用して、Access ControlモードでMongoDBを起動するよう設定します。これも全MongoDBサーバーに設定します。
... security: authorization: enabled clusterAuthMode: keyFile keyFile: /var/lib/mongo/keyFile ...
コンフィグファイルを編集した後、MongoDBを再起動します。
検証
挿入と検索
データを挿入してみます。
rs0:PRIMARY> use test switched to db test rs0:PRIMARY> db.zunda.insertOne( ... { name: "Tohoku Zunko" }, ... { writeConcern: { w: 1, j: true, wtimeout: 1000 } } ... ) { "acknowledged" : true, "insertedId" : ObjectId("5ee923f8c9f1bdf73095d5da") }
writeConcern
オプションは、分散環境下での書き込み一貫性を保証するオプションです。各パラメーターの説明のざっくり説明はこちら。
The value specified to w determines the number of replica set members that must acknowledge the write before returning success. For each eligible replica set member, the j option determines whether the member acknowledges writes after applying the write operation in memory or after writing to the on-disk journal.
上記クエリの場合、1台以上のサーバーのディスク(journal)に書き込みされることで処理成功となります。
Write処理はPrimaryのMongoDBにしかできませんが、Read処理はSecondaryでも実施できます。DefaultではPrimaryに読み込みしにいくそうで、明示的にSecondaryを指定する場合、 readPref
オプションを利用するとのこと。
rs0:PRIMARY> db.zunda.find().readPref( "secondary" ) { "_id" : ObjectId("5ee923f8c9f1bdf73095d5da"), "name" : "Tohoku Zunko" }
failoverを試してみる
実際にfailoverさせてみます。PrimaryであるMongoDBを停止します。
$ sudo systemctl stop mongod
SecondaryであったMongoDBに接続して、ステータスを確認します。Primaryのサーバーが移動していますね。
> rs.status().members [ { "_id" : 0, "name" : "192.168.0.10:27017", "health" : 0, "state" : 8, "stateStr" : "(not reachable/healthy)", "uptime" : 0, "optime" : { "ts" : Timestamp(0, 0), "t" : NumberLong(-1) }, "optimeDurable" : { "ts" : Timestamp(0, 0), "t" : NumberLong(-1) }, "optimeDate" : ISODate("1970-01-01T00:00:00Z"), "optimeDurableDate" : ISODate("1970-01-01T00:00:00Z"), "lastHeartbeat" : ISODate("2020-06-16T20:12:06.845Z"), "lastHeartbeatRecv" : ISODate("1970-01-01T00:00:00Z"), "pingMs" : NumberLong(1), "lastHeartbeatMessage" : "Error connecting to 192.168.0.10:27017 :: caused by :: Connection refused", "syncingTo" : "", "syncSourceHost" : "", "syncSourceId" : -1, "infoMessage" : "", "configVersion" : -1 }, { "_id" : 1, "name" : "192.168.0.20:27017", "health" : 1, "state" : 1, "stateStr" : "PRIMARY", "uptime" : 3104, "optime" : { "ts" : Timestamp(1592338326, 1), "t" : NumberLong(9) }, "optimeDate" : ISODate("2020-06-16T20:12:06Z"), "syncingTo" : "", "syncSourceHost" : "", "syncSourceId" : -1, "infoMessage" : "could not find member to sync from", "electionTime" : Timestamp(1592338276, 1), "electionDate" : ISODate("2020-06-16T20:11:16Z"), "configVersion" : 2, "self" : true, "lastHeartbeatMessage" : "" }, { "_id" : 2, "name" : "192.168.0.30:27017", "health" : 1, "state" : 7, "stateStr" : "ARBITER", "uptime" : 3065, "lastHeartbeat" : ISODate("2020-06-16T20:12:06.674Z"), "lastHeartbeatRecv" : ISODate("1970-01-01T00:00:00Z"), "pingMs" : NumberLong(2), "lastHeartbeatMessage" : "", "syncingTo" : "", "syncSourceHost" : "", "syncSourceId" : -1, "infoMessage" : "", "configVersion" : 2 } ]
停止したMongoDBサーバーを起動し直すと、Secondaryとなります。