MongoDB で 2台構成 の レプリカセット を 構築する 方法

0 件のコメント

今回は「MongoDB で 2台構成 の レプリカセット を 作成する方法」についてまとめます。

…いきなりですが、MongoDB で 2台構成 のレプリカセットを作ることはできません。 低スペックで構いませんので必ず3台構成にする必要があります。

概要

今回は物理2台サーバーでレプリカセットを構築する方法について実際のコードを交えながら見ていきます。 …が、先に述べた通りMongoDBにおけるレプリカセットは最小構成が3台なので、物理2台の場合もう1台追加する必要があります。 追加するサーバーはデータを持たないインスタンスのみが稼働するサーバーで良いので、スペックの低いサーバー(アプリケーションサーバーや管理サーバーなど)でも問題ありません。 このデータを保持せず投票権だけを持つサーバーをアービターサーバーと呼びます。 アービターをプライマリやセカンダリに同居させるのも、冗長性の観点で意味がないので行わないでください。

今回は以下の図のようなサーバー3台構成(プライマリ1台、セカンダリ1台、アービター1台)のMongoDBレプリカセットを構築していきます。

今回は動作確認をお試しで行いたいので、以下の制約をつけた環境でのサンプルになります。

  • 1台の物理マシンの中に上記3インスタンスを構築
  • 認証制御は行わない

どちらかというと本番向けというよりは開発環境向けのデータベースサーバー構築になります。

レプリカセットの構築

今回はテスト用として物理1台の中に3台分のMongoDBインスタンスを作成するサンプルです。 実運用だと先述の通り「冗長性を持たせたい高スペック2サーバーそれぞれに1インスタンス(プライマリ、セカンダリ)、低スペックな1サーバーに1インスタンス(アービター)」になるはずなので、一部設定は読み替えていただければと思います。

レプリカセットメンバーの準備

  1. 各インスタンス用にフォルダ・ファイルを作成

    C:\mongodb
      │
      ├ \server1
      │  ├ \data
      │  ├ \log
      │  └ mongod.srver1.cfg
      │     
      ├ \server2
      │  ├ \data
      │  ├ \log
      │  └ mongod.server2.cfg
      │     
      └ \server3
          ├ \data
          ├ \log
          └ mongod.server3.cfg
    
  2. 各インスタンスの設定ファイル(mongod.serverN.cfg)を修正

    1サーバー中に複数インスタンス生成しているため、ポート番号を分けています。 1サーバー1インスタンスであればポートはすべて同じ (27017 など) でも問題ないです。

    レプリカセット名(replSetName)はすべて共通(今回は rs0 )で設定しておきます。 また、開発環境向けであれば bindIpAll 設定を入れておくとバインドIPを列挙しなくてよいので設定が簡単です。 プロダクション向けは bindIp を面倒でも設定しておくほうが良いと思いますが…。 アービターサーバーにはファイル保存しないので storage.journal.enabled: false を設定しておきます。

    mongod.srver1.cfg

    systemLog:
      destination: file
      path: C:\mongodb\server1\log\mongod.srver1.log
      logAppend: true
      logRotate: "rename"
    storage:
      dbPath: C:\mongodb\server1\data
    replication:
      replSetName: "rs0"
    net:
      bindIpAll: true
      port: 8001
    

    mongod.server2.cfg

    systemLog:
      destination: file
      path: C:\mongodb\server2\log\mongod.server2.log
      logAppend: true
      logRotate: "rename"
    storage:
      dbPath: C:\mongodb\server2\data
    replication:
      replSetName: "rs0"
    net:
      bindIpAll: true
      port: 8002
    

    mongod.server3.cfg

    systemLog:
      destination: file
      path: C:\mongodb\server3\log\mongod.server3.log
      logAppend: true
      logRotate: "rename"
    storage:
      dbPath: C:\mongodb\server3\data
      journal:
        enabled: false
    replication:
      replSetName: "rs0"
    net:
      bindIpAll: true
      port: 8003
    
  3. 各インスタンスを起動

    分かりやすいようコマンドプロンプトを3つ立ち上げてそれぞれのインスタンスを起動します。

    mongod --config "C:\mongodb\server1\mongod.srver1.cfg"
    
    mongod --config "C:\mongodb\server2\mongod.server2.cfg"
    
    mongod --config "C:\mongodb\server3\mongod.server3.cfg"
    

レプリカセットの初期化

  1. いずれかのインスタンスにログイン

    今回は以下のコマンドを実行して server1 へログインします。

    mongo --host 127.0.0.1:27001
    
  2. レプリカセット初期化処理の実行

    mongoコマンドの rs.initiate() を実行することでレプリカセットを初期化します。 渡したい引数はあらかじめ config オブジェクトへ代入してから渡します(そのほうが誤りを訂正できるので)。 アービターサーバーには arbiterOnly: true を付与します。

    > config = {
       _id : "rs0",
       members: [
          { _id: 0, host: "127.0.0.1:8001" },
          { _id: 1, host: "127.0.0.1:8002" },
          { _id: 2, host: "127.0.0.1:8003", arbiterOnly: true },
       ]
    }
    > rs.initiate(config)
    

以上で設定は完了してレプリカセットが利用できる状態になりました。 次の章で簡単な動作確認をしてみます。

レプリカセットの動作確認

今回は動作確認として以下の以下の3つを行ってみます。

  • レプリカセットの設定確認
  • レプリカセットのステータス確認
  • フェールオーバーの動作確認

レプリカセットの設定確認

  1. コマンドプロンプトで rs.config() を実行

    > rs.config()
    
  2. コマンド実行結果としてレプリカセット設定が表示されることを確認

    {
      "_id" : "rs0",
      "version" : 1,
      "protocolVersion" : NumberLong(1),
      "members" : [
        {
          "_id" : 0,
          "host" : "127.0.0.1:8001",
          "arbiterOnly" : false,
          "buildIndexes" : true,
          "hidden" : false,
          "priority" : 1,
          "tags" : {
    
          },
          "slaveDelay" : NumberLong(0),
          "votes" : 1
        },
        {
          "_id" : 1,
          "host" : "127.0.0.1:8002",
          "arbiterOnly" : false,
          "buildIndexes" : true,
          "hidden" : false,
          "priority" : 1,
          "tags" : {
    
          },
          "slaveDelay" : NumberLong(0),
          "votes" : 1
        },
        {
          "_id" : 2,
          "host" : "127.0.0.1:8003",
          "arbiterOnly" : true,
          "buildIndexes" : true,
          "hidden" : false,
          "priority" : 0,
          "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("5a76f437386f5b1a0a9f8f01")
      }
    }
    

サーバーのステータス確認

  1. コマンドで rs.status() を実行

    > rs.status()
    
  2. コマンド実行結果として現在のサーバーステータスが表示されることを確認

    {
      "set" : "rs0",
      "date" : ISODate("2018-02-04T11:53:38.972Z"),
      "myState" : 1,
      "term" : NumberLong(1),
      "heartbeatIntervalMillis" : NumberLong(2000),
      "optimes" : {
        "lastCommittedOpTime" : {
          "ts" : Timestamp(0, 0),
          "t" : NumberLong(-1)
        },
        "appliedOpTime" : {
          "ts" : Timestamp(1517745207, 1),
          "t" : NumberLong(-1)
        },
        "durableOpTime" : {
          "ts" : Timestamp(1517745207, 1),
          "t" : NumberLong(-1)
        }
      },
      "members" : [
        {
          "_id" : 0,
          "name" : "127.0.0.1:8001",
          "health" : 1,
          "state" : 1,
          "stateStr" : "PRIMARY",
          "uptime" : 117,
          "optime" : {
            "ts" : Timestamp(1517745207, 1),
            "t" : NumberLong(-1)
          },
          "optimeDate" : ISODate("2018-02-04T11:53:27Z"),
          "infoMessage" : "could not find member to sync from",
          "electionTime" : Timestamp(1517745218, 1),
          "electionDate" : ISODate("2018-02-04T11:53:38Z"),
          "configVersion" : 1,
          "self" : true
        },
        {
          "_id" : 1,
          "name" : "127.0.0.1:8002",
          "health" : 1,
          "state" : 2,
          "stateStr" : "SECONDARY",
          "uptime" : 11,
          "optime" : {
            "ts" : Timestamp(1517745207, 1),
            "t" : NumberLong(-1)
          },
          "optimeDurable" : {
            "ts" : Timestamp(1517745207, 1),
            "t" : NumberLong(-1)
          },
          "optimeDate" : ISODate("2018-02-04T11:53:27Z"),
          "optimeDurableDate" : ISODate("2018-02-04T11:53:27Z"),
          "lastHeartbeat" : ISODate("2018-02-04T11:53:38.520Z"),
          "lastHeartbeatRecv" : ISODate("2018-02-04T11:53:34.281Z"),
          "pingMs" : NumberLong(0),
          "configVersion" : 1
        },
        {
          "_id" : 2,
          "name" : "127.0.0.1:8003",
          "health" : 1,
          "state" : 7,
          "stateStr" : "ARBITER",
          "uptime" : 11,
          "lastHeartbeat" : ISODate("2018-02-04T11:53:38.524Z"),
          "lastHeartbeatRecv" : ISODate("2018-02-04T11:53:34.205Z"),
          "pingMs" : NumberLong(0),
          "configVersion" : 1
        }
      ],
      "ok" : 1,
      "operationTime" : Timestamp(1517745207, 1),
      "$clusterTime" : {
        "clusterTime" : Timestamp(1517745218, 1),
        "signature" : {
          "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
          "keyId" : NumberLong(0)
        }
      }
    }
    

フェールオーバーの動作確認

MongoDBではプライマリサーバーがダウンしたとき(通信不能になったとき)、自動でセカンダリサーバーがプライマリサーバーに昇格するようになっています。 今回はプライマリサーバーを強制的に落としてセカンダリサーバーがプライマリサーバーに昇格することを確認します。

  1. 現在のステータスを確認

    先述の rs.status() で状況を確認します。 結果出力内容は一部省略しています。

    PRIMARY> rs.status()
    {
      ...
      "members" : [
        {
          "_id" : 0,
          "name" : "127.0.0.1:8001",
          "health" : 1,
          "state" : 1,
          "stateStr" : "PRIMARY",
          ...
        },
        {
          "_id" : 1,
          "name" : "127.0.0.1:8002",
          "health" : 1,
          "state" : 2,
          "stateStr" : "SECONDARY",
          ...
        },
        {
          "_id" : 2,
          "name" : "127.0.0.1:8003",
          "health" : 1,
          "state" : 7,
          "stateStr" : "ARBITER",
          ...
        }
      ],
      ...
    }
    
  2. プライマリサーバーのコマンドプロンプトを停止

    先ほどの rs.status() の結果から _id: 0 のサーバーがプライマリサーバーだとわかりましたので、 _id: 0 のサーバーを起動しているコマンドプロンプト上で Ctrl + C を実行し、停止させます。

  3. ステータスの確認

    ハートビート(生死確認)は2秒間隔なので、3秒程度待ってから rs.status() を実行します。 セカンダリサーバーのどちらかがプライマリサーバーに昇格していることが確認できます。

    PRIMARY> rs.status()
    {
      ...
      "members" : [
        {
          "_id" : 0,
          "name" : "127.0.0.1:8001",
          "health" : 0,
          "state" : 8,
          "stateStr" : "(not reachable/healthy)",
          ...
        },
        {
          "_id" : 1,
          "name" : "127.0.0.1:8002",
          "health" : 1,
          "state" : 1,
          "stateStr" : "PRIMARY",
          ...
        },
        {
          "_id" : 2,
          "name" : "127.0.0.1:8003",
          "health" : 1,
          "state" : 7,
          "stateStr" : "ARBITER",
          ...
        }
      ],
      ...
    }
    

今回は「MongoDBの2台構成レプリカセットの構築」についてまとめました。 ポイントは以下の通りです。

  • 物理2台構成のレプリカセットは作成できない。
    高スペック2台構成(プライマリ、セカンダリ)+低スペック1台(アービター)の3台構成にする。
  • 1台サーバーに3インスタンスの場合、ポートを分ける。
  • アービターサーバーには以下の設定を入れる
    • storage.journal.enabled: false
    • arbiterOnly: true

参考になったでしょうか? 本記事がお役に立っていると嬉しいです!!

参考記事

最後に… このブログに興味を持っていただけた方は、 ぜひ 「Facebookページ に いいね!」または 「Twitter の フォロー」 お願いします!!