MongoDB レプリケーション設定

スポンサーリンク

MongoDBのレプリケーション設定についてまとめます。MongoDBはPrimary ServerとSecodary Serverの複数のサーバ構成を採用する事によって、冗長性と負荷分散を実現します。なお、MongoDBのPrimary Serverの決定は投票制を採用していますので、必ず奇数台のMongoDBサーバを構築ください。

前提

公式ドキュメント

参考となる公式ドキュメントを以下に示します。

動作確認済環境

  • 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}
    ]
  }
)
タイトルとURLをコピーしました