MongoDBのレプリケーション設定についてまとめます。MongoDBはPrimary ServerとSecodary Serverの複数のサーバ構成を採用する事によって、冗長性と負荷分散を実現します。なお、MongoDBのPrimary Serverの決定は投票制を採用していますので、必ず奇数台のMongoDBサーバを構築ください。
前提
公式ドキュメント
参考となる公式ドキュメントを以下に示します。
- Replication
- Deploy a Replica Set
- Adjust Priority for Replica Set Member
- Prevent Secondary from Becoming Primary
- Convert a Secondary to an Arbiter
動作確認済環境
- Rocky Linux 8.6
- MongoDB Server 6.0.2
構成図
以下の構成で動作確認をします。
172.16.1.0/24 +-----------------+-----------------+ | | | ens192 | .10 ens192 | .20 ens192 | .30 +------+------+ +------+------+ +------+------+ | linux010 | | linux020 | | linux030 | | (Rocky8.6) | | (Rocky8.6) | | (Rocky8.6) | | (Primary) | | (Secondary) | | (Secondary) | +-------------+ +-------------+ +-------------+
最小限構成例
最小限の構成であるPrimary 1台とSecondary 2台の構成例を示します。
Replicationの準備
Replicationを設定する前にデフォルト設定の挙動を観察しましょう。
まずは、socketの状態を確認します。デフォルト設定では127.0.0.1(localhost)からのみ接続可能な状態になっています。これではReplicationの通信は出来ませんので、socketの設定を変更する必要があります。
[root@linux010 ~]# ss -ano | grep 27017 u_str LISTEN 0 128 /tmp/mongodb-27017.sock 33466 * 0 tcp LISTEN 0 128 127.0.0.1:27017 0.0.0.0:* [root@linux010 ~]#
mongoshコマンドでMongoDBへ接続後、Replicationの設定を表示する「rs.conf()」コマンドを実行します。すると、「not running with –replSet」とのエラーメッセージが出力され、デフォルトの状態ではReplicationが動作していない事が分かります。
test> rs.conf() MongoServerError: not running with --replSet test>
/etc/mongod.confを編集し、レプリケーションが可能になるようにします。以下に設定例とパラメタの一覧を記します。
パラメタ | 意味 |
---|---|
net.bindIp | MongoDBがListenするIPアドレス。カンマ区切りで複数指定可能。0.0.0.0を指定した場合は、全てのIPアドレスがListen状態になる。 |
replication.replSetName | レプリカセットの名前。レプリケーションを実行するサーバ間で同じ名前を指定する必要がある。 |
replication.oplogSizeMB | oplog(operations log)はデータベースへの操作を記録するログ。oplogSizeMBはoplogのサイズを指定するパラメタ。 |
# vi /etc/mongod.conf <omitted> # network interfaces net: port: 27017 bindIp: 0.0.0.0 #bindIp: 127.0.0.1 # Enter 0.0.0.0,:: to bind to all IPv4 and IPv6 addresses or, alternatively, use the net.bindIpAll setting. #security: #operationProfiling: #replication: replication: replSetName: "rs0" oplogSizeMB: 1024 #sharding: ## Enterprise-Only Options #auditLog: #snmp:
MongoDBを再起動させ、設定を反映させます。
systemctl restart mongod.service
tcp27017が0.0.0.0に対してListenの状態に変わった事を確認します。
[root@linux010 ~]# ss -ano | grep 27017 u_str LISTEN 0 128 /tmp/mongodb-27017.sock 38247 * 0 tcp LISTEN 0 128 0.0.0.0:27017 0.0.0.0:*
mongoshコマンドでMongoDBへ接続後、Replicationの設定を表示する「rs.conf()」コマンドを実行します。すると、「not running with –replSet」とのエラーメッセージから「no replset config has been received」のエラーメッセージに変わった事が分かります。Replicationは起動しているものの未設定の状態である事が分かります。
test> rs.conf() MongoServerError: no replset config has been received test>
以上の設定を3台のMongoDBサーバ全てに対して実行します。
Replicationの開始
3台のMongoDBサーバのうち1台へmongoshコマンドで接続します。rs.initiateメソッドを使用すると、レプリケーションが開始されます。
デフォルト設定のポート番号を使用する場合は、ポート番号指定は省略可能です。
rs.initiate( { _id : "rs0", members: [ { _id: 0, host: "172.16.1.10:27017" } ] })
レプリケーションが開始されると、「direct: other」のようにMongoDBの状態がプロンプトに表示されます。今現在は1台体制なので、「direct: other」と表示されますが、3台以上の体制になりprimaryやsecondaryが選出できる状態になると「direct: primary」のように表示が変わります。
test> rs.initiate( { ... _id : "rs0", ... members: [ ... { _id: 0, host: "172.16.1.10" } ... ] ... }) { ok: 1 } rs0 [direct: other] test>
レプリケーションの設定はrs.conf()コマンドで確認できます。
test> rs.conf(); { _id: 'rs0', version: 1, term: 1, members: [ { _id: 0, host: '172.16.1.10:27017', arbiterOnly: false, buildIndexes: true, hidden: false, priority: 1, tags: {}, secondaryDelaySecs: Long("0"), votes: 1 } <omitted>
Replica Set メンバーの追加
rs.add()メソッドでReplica Set メンバーを追加できます。
rs.add("172.16.1.20:27017") rs.add("172.16.1.30:27017")
操作後、Replica Setのメンバーが3台になった事を確認します。
rs0 [direct: primary] test> rs.config() { _id: 'rs0', version: 4, term: 1, members: [ { _id: 0, host: '172.16.1.10:27017', arbiterOnly: false, buildIndexes: true, hidden: false, priority: 1, tags: {}, secondaryDelaySecs: Long("0"), votes: 1 }, { _id: 1, host: '172.16.1.20:27017', arbiterOnly: false, buildIndexes: true, hidden: false, priority: 1, tags: {}, secondaryDelaySecs: Long("0"), votes: 1 }, { _id: 2, host: '172.16.1.30:27017', arbiterOnly: false, buildIndexes: true, hidden: false, priority: 1, tags: {}, secondaryDelaySecs: Long("0"), votes: 1 } ], <omitted>
レプリケーションの状態はrs.status()コマンドで確認できます。どの機器がPrimaryになったか分からない時は、rs.status()を使用すると良いでしょう。stateStrを見ると、PrimaryかSecondaryかを判断できます。
rs0 [direct: secondary] test> rs.status(); { set: 'rs0', date: ISODate("2022-11-07T12:30:57.798Z"), myState: 2, term: Long("1"), <omitted> members: [ { _id: 0, name: '172.16.1.10:27017', health: 1, state: 1, stateStr: 'PRIMARY', uptime: 98, <omitted>
oplogの観察
データがレプリケーションされる事を確認するため、Primaryにて動作確認目的のInsert文を発行します。
db.inventory.insertOne( { _id: 0, item: "canvas", qty: 100, tags: ["cotton"], size: { h: 28, w: 35.5, uom: "cm" } } )
MongoDBはoplog(operations log)にデータベースへの変更内容を記録し、oplogをPrimaryからSecondaryへ転送する事でレプリケーションを実現します。MySQLのbinlog/relaylogやPostgresのWALを知っている方は、これらをイメージすると理解しやすいでしょう。
oplogの実態は、localデータベースのoplogコレクションです。以下のような操作をすると、最新(末尾)のopelogを観察する事ができます。確かに、さきほどInsert文を発行したデータがopelogとして記録されている事が確認できます。
rs0 [direct: primary] test> use local rs0 [direct: primary] local> db.oplog.rs.find({op:"i"}).sort({$natural: -1}).limit(1); [ { lsid: { id: new UUID("2112c377-a69c-4e81-ab91-1a8bcaf53126"), uid: Binary(Buffer.from("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "hex"), 0) }, txnNumber: Long("4"), op: 'i', ns: 'test.inventory', ui: new UUID("35f194bd-cb9d-476a-b3ab-1af20245a44f"), o: { _id: 0, item: 'canvas', qty: 100, tags: [ 'cotton' ], size: { h: 28, w: 35.5, uom: 'cm' } }, o2: { _id: 0 }, stmtId: 0, ts: Timestamp({ t: 1667826973, i: 2 }), t: Long("1"), v: Long("2"), wall: ISODate("2022-11-07T13:16:13.123Z"), prevOpTime: { ts: Timestamp({ t: 0, i: 0 }), t: Long("-1") } } ] rs0 [direct: primary] local>
PrimaryからSecondaryへ正常にoplogが転送されているならば、Primaryと同じopelogがSecondaryでも見る事ができます。
rs0 [direct: secondary] test> use local switched to db local rs0 [direct: secondary] local> db.oplog.rs.find({op:"i"}).sort({$natural: -1}).limit(1); [ { lsid: { id: new UUID("2112c377-a69c-4e81-ab91-1a8bcaf53126"), uid: Binary(Buffer.from("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "hex"), 0) }, txnNumber: Long("4"), op: 'i', ns: 'test.inventory', ui: new UUID("35f194bd-cb9d-476a-b3ab-1af20245a44f"), o: { _id: 0, item: 'canvas', qty: 100, tags: [ 'cotton' ], size: { h: 28, w: 35.5, uom: 'cm' } }, o2: { _id: 0 }, stmtId: 0, ts: Timestamp({ t: 1667826973, i: 2 }), t: Long("1"), v: Long("2"), wall: ISODate("2022-11-07T13:16:13.123Z"), prevOpTime: { ts: Timestamp({ t: 0, i: 0 }), t: Long("-1") } } ] rs0 [direct: secondary] local>
障害発生時の挙動確認
Primary機にて、rebootコマンドを発行します。
# reboot
再起動後、rs.status()コマンドでPrimaryが切り替わった事を確認します。ワンライナーで整形しつつ出力する操作例は以下の通りです。
[root@linux020 ~]# mongosh --quiet --eval "JSON.stringify(rs.status())" | jq ".members[] | {_id,name,stateStr}" { "_id": 0, "name": "172.16.1.10:27017", "stateStr": "SECONDARY" } { "_id": 1, "name": "172.16.1.20:27017", "stateStr": "PRIMARY" } { "_id": 2, "name": "172.16.1.30:27017", "stateStr": "SECONDARY" }
Replica Set Memberの変更
Priorityの変更
以下のような操作で、Replica Set Memberのpriorityを変更する事ができます。priorityが大きいサーバほどPrimaryに選出されやすくなります。
priority = 0とするとPrimaryとして選出されなくなります。
cfg = rs.conf() cfg.members[0].priority = 4 cfg.members[1].priority = 2 cfg.members[2].priority = 1 rs.reconfig(cfg)
priorityの値が変わった事を確認します。
[root@linux020 ~]# mongosh --quiet --eval "JSON.stringify(rs.config())" | jq ".members[] | {_id,host,priority,votes}" { "_id": 0, "host": "172.16.1.10:27017", "priority": 4, "votes": 1 } { "_id": 1, "host": "172.16.1.20:27017", "priority": 2, "votes": 1 } { "_id": 2, "host": "172.16.1.30:27017", "priority": 1, "votes": 1 } [root@linux020 ~]# mongosh --quiet --eval "JSON.stringify(rs.status())" | jq ".members[] | {_id,name,stateStr}" { "_id": 0, "name": "172.16.1.10:27017", "stateStr": "SECONDARY" } { "_id": 1, "name": "172.16.1.20:27017", "stateStr": "PRIMARY" } { "_id": 2, "name": "172.16.1.30:27017", "stateStr": "SECONDARY" } [root@linux020 ~]# Primamry機を再起動させます。 # reboot
Priorityが大きい機器がPrimaryとして再選出された事を確認します。
[root@linux010 ~]# mongosh --quiet --eval "JSON.stringify(rs.config())" | jq ".members[] | {_id,host,priority,votes}" { "_id": 0, "host": "172.16.1.10:27017", "priority": 4, "votes": 1 } { "_id": 1, "host": "172.16.1.20:27017", "priority": 2, "votes": 1 } { "_id": 2, "host": "172.16.1.30:27017", "priority": 1, "votes": 1 } [root@linux010 ~]# mongosh --quiet --eval "JSON.stringify(rs.status())" | jq ".members[] | {_id,name,stateStr}" { "_id": 0, "name": "172.16.1.10:27017", "stateStr": "PRIMARY" } { "_id": 1, "name": "172.16.1.20:27017", "stateStr": "SECONDARY" } { "_id": 2, "name": "172.16.1.30:27017", "stateStr": "SECONDARY" } [root@linux010 ~]#
本ページでは説明を省略しますが、上記以外にもNon-Voting Replica Set MemberやHidden Replica Set Memberなどの設定も可能です。詳細は「https://www.mongodb.com/docs/v6.0/administration/replica-set-member-configuration/」を参照ください。
Arbiter
MongoDBはデータベースを持たないものの投票だけするメンバーArbiterを設定する事もできます。冗長性が確保された最小構成ならば、Primary 1台/Secondary 1台/Arbiter 1台の構成でも良いでしょう。
それでは、SecondaryをArbiterに変える操作例を示します。
まずはPrimary機に接続し、Secondaryメンバーのうち1つを削除します。
rs0 [direct: primary] test> rs.remove("172.16.1.30:27017") { ok: 1, '$clusterTime': { clusterTime: Timestamp({ t: 1667860574, i: 1 }), signature: { hash: Binary(Buffer.from("0000000000000000000000000000000000000000", "hex"), 0), keyId: Long("0") } }, operationTime: Timestamp({ t: 1667860574, i: 1 }) } rs0 [direct: primary] test>
SecondaryからArbiterへ変更する機器へログインします。Arbiterはデータベースを持たず投票だけするメンバーですので、データベースを削除します。
systemctl stop mongod.service rm -rf /var/lib/mongo mkdir /var/lib/mongo chown mongod:mongod /var/lib/mongo
oplogも不要ですので、/etc/mongod.confをテキストエディタで開きoplogSizeMBをコメントアウトします。
# vi /etc/mongod.conf <omitted> #operationProfiling: #replication: replication: replSetName: "rs0" #oplogSizeMB: 1024 #sharding: ## Enterprise-Only Options #auditLog: #snmp:
設定を反映させます。
systemctl start mongod.service Primary機にてmongoshコマンドで接続し、rs.addArb()コマンドでArbiterを追加できます。しかし、デフォルトの状態では以下のようなメッセージが出力されます。 rs0 [direct: primary] test> rs.addArb("172.16.1.30:27017") MongoServerError: Reconfig attempted to install a config that would change the implicit default write concern. Use the setDefaultRWConcern command to set a cluster-wide write concern and try the reconfig again. rs0 [direct: primary] test>
Arbiterを追加するには、setDefaultRWConcernというパラメタを定義しなければならないようです。以下のようなコマンドでsetDefaultRWConcernを定義します。
setDefaultRWConcernの意味はMongoDB Write Concern / Read Concern」を参照ください。
db.adminCommand({ "setDefaultRWConcern" : 1, "defaultWriteConcern" : { "w" : 1 } })
再度、addArb()コマンドを実行を試みると、以下のように成功した旨の出力が得られます。
rs0 [direct: primary] test> rs.addArb("172.16.1.30:27017") { ok: 1, '$clusterTime': { clusterTime: Timestamp({ t: 1667860917, i: 1 }), signature: { hash: Binary(Buffer.from("0000000000000000000000000000000000000000", "hex"), 0), keyId: Long("0") } }, operationTime: Timestamp({ t: 1667860917, i: 1 }) } rs0 [direct: primary] test>
補足
一括設定
前述の操作例はrs.add()やrs.remove()などを操作で、replica setメンバーを1台ずつ追加していました。しかし、以下のように一括でreplica setメンバーを定義する方法もあります。
rs.initiate( { _id: "rs0", members: [ { _id : 0, host : "172.16.1.10:27017", priority : 4 }, { _id : 1, host : "172.16.1.20:27017", priority : 2 }, { _id : 2, host : "172.16.1.30:27017", priority : 1 } ] } )
もしArbiterを定義したいならば、以下のように操作します。
rs.initiate( { _id: "rs0", members: [ { _id : 0, host : "172.16.1.10:27017", priority : 4 }, { _id : 1, host : "172.16.1.20:27017", priority : 2 }, { _id : 2, host : "172.16.1.30:27017", priority : 1, arbiterOnly : true} ] } )