ApsaraDB for MongoDB のレプリカセットは、プライマリノードと複数のセカンダリノードで構成されるmongod プロセスのグループです。 MongoDB ドライバーは、プライマリノードにのみデータを書き込みます。 次に、データはプライマリノードからセカンダリノードに同期されます。 この方法により、レプリカセット内のすべてのノード間でデータの整合性が確保されます。 そのため、レプリカセットにより可用性が向上します。

下図は、MongoDB の公式文書から抜粋したものです。 図では、1 つのプライマリノードと 2 つのセカンダリノードを含む典型的な MongoDB レプリカセットを示しています。

プライマリノードの選出 (1)

レプリカセットは、mongo シェルでreplSetInitiateコマンドを実行するか、またはrs.initiate()コマンドを実行することで、初期化されます。 レプリカセットが初期化されると、メンバーは互いにハートビートメッセージを送信し、プライマリノードの選出を開始します。 メンバーの過半数から投票を受け取ったノードがプライマリノードになり、他ノードはセカンダリノードになります。

レプリカセットの初期化

    config = {
        _id : "my_replica_set",
        members : [
             {_id : 0, host : "rs1.example.net:27017"},
             {_id : 1, host : "rs2.example.net:27017"},
             {_id : 2, host : "rs3.example.net:27017"},
       ]
    }
    rs.initiate(config)

過半数の定義

投票メンバーのグループは、総メンバー数の平均を超えるメンバーが含まれている場合にのみ、過半数と見なされます。 レプリカセット内のメンバーの数がすべての投票メンバーの平均数以下である場合、選出は実施できません。 この場合、レプリカセットにデータを書き込むことはできません。

投票メンバーの数 過半数 最大失敗ノード数
1 1 0
2 2 0
3 2 1
4 3 1
5 3 2
6 4 2
7 4 3

レプリカセットのメンバー数を奇数に設定することを推奨します。 上表は、ノードが 3 つのレプリカセットとノードが 4 つのレプリカセットが共に、ノードの障害を 1 つだけ許容することを示しています。 これら 2 つのレプリカセットのサービスの可用性は同じです。 ただし、ノードが 4 つのレプリカセットは、より信頼性の高いデータストレージを提供します。

特別なセカンダリノード

レプリカセットのセカンダリノードは、プライマリノードの選出に参加します。 セカンダリノードをプライマリノードとして選出することもできます。 プライマリノードに書き込まれた最新データはセカンダリノードと同期され、すべてのノード間でデータの整合性が確保されます。

セカンダリノードからデータを読み取ることもできます。 したがって、セカンダリノードをレプリカセットに追加して、レプリカセットの読み取りパフォーマンスとサービス可用性を向上させることができます。 ApsaraDB for MongoDB では、さまざまなシナリオの要件を満たすよう、レプリカセットのセカンダリノードを構成できます。

  • アービター

    アービターノードは、投票者としてのみ選出に参加します。 プライマリノードとして選出されたり、プライマリノードからのデータを同期したりすることはできません。

    プライマリノードを 1 つ、セカンダリノードを 1 つ含むレプリカセットをデプロイすると仮定します。 ある 1 つのノードに障害が発生した場合、プライマリノードを選出できません。 その結果、レプリカセットは使用できなくなります。 この場合、レプリカセットにアービターノードを追加して、プライマリノードの選出を有効にすることができます。

    アービターノードは、データを格納しない軽量ノードです。 レプリカセットのメンバー数が偶数の場合では、アービターノードを追加してレプリカセットの可用性を高めることを推奨します。

  • 優先度 0

    プライマリノード選出において優先度 0 のノードは、プライマリノードとして選出できません。

    データセンター A とデータセンター B の双方にノードを含むレプリカセットをデプロイすると仮定します。 選択されたプライマリノードがデータセンター A で確実にデプロイされるようにするには、データセンター B のレプリカセットメンバーの優先度を 0 に設定します。
    データセンター B のメンバーの優先度を 0 に設定した場合、レプリカセットノードの大部分をデータセンター A にデプロイすることを推奨します。 レプリカセットノードの大部分がデータセンター A にデプロイされていない場合、ネットワークパーティショニング中にプライマリノードの選出に失敗する恐れがあります。
  • 投票 0

    MongoDB 3.0 では、レプリカセットには最大 50 のメンバーが含まれ、プライマリノードを選出する際、最大 7 つのメンバーが投票できます。 投票する予定のないメンバーについては、members [n] .votes 属性を 0 に設定する必要があります。

  • 非表示

    レプリカセット内の非表示メンバーは、優先度が 0 であるため、プライマリノードとして選出できません。 非表示ノードは、MongoDB ライバーからは見えません。

    非表示ノードを使用して、データをバックアップしたり、オフラインの計算タスクを実行したりできます。 非表示ノードが MongoDB ドライバーからのリクエストを処理しないので、これはレプリカセットのサービスには影響しません。

  • 遅延

    遅延ノードは非表示ノードである必要があります。 遅延ノードのデータは、プライマリノード上のデータの以前の状態を反映しています。 1 時間の遅延を構成すると、遅延ノードのデータは 1 時間前のプライマリノードのデータと同じになります。

    したがって、プライマリノードに誤ったデータまたは無効なデータを書き込んだ場合、遅延ノードのデータを使用して、プライマリノードのデータを以前の状態に復元できます。

プライマリノードの選出 (2)

プライマリノードの選出は、レプリカセットの初期化後だけでなく、次のシナリオでもトリガーされます。

  • レプリカセットの再構成
    プライマリノードに障害が発生するか、または自発的にステップダウンしてセカンダリノードになると、プライマリノードの選出がトリガーされます。 プライマリノードの選出は、ノード間のハートビートメッセージ、ノードの優先順位、最後の oplog エントリが生成された時刻などのさまざまな要因に左右されます。
    • ノードの優先順位

      ノードはすべて、優先度が最も高いノードに投票する傾向があります。 優先順位 0 のノードはプライマリノードの選出をトリガーできません。 セカンダリノードの優先度がプライマリノードよりも高く、セカンダリノードの最新ログエントリとプライマリノードの最新ログエントリの時間差が 10 秒以内である場合、プライマリノードがステップダウンします。 この場合、このセカンダリノードはプライマリノードの候補になります。

    • Optime

      oplog エントリが最新のセカンダリノードのみがプライマリノードとして選出されるのに適しています。

  • ネットワークパーティショニング

    過半数の投票ノードに接続されているノードのみをプライマリノードとして選出できます。 プライマリノードがレプリカセット内の他ノードの大部分から切断されている場合、プライマリノードは自発的にステップダウンして、セカンダリノードになります。 レプリカセットには、ネットワークパーティショニング中の短期間にわたり複数のプライマリノードが含まれる場合があります。 MongoDB ドライバーがデータを書き込む際、ノードの大部分に接続されているプライマリノードからのみデータ同期を許可するポリシーを設定することを推奨します。

データ同期

データは、oplog に基づいてプライマリノードからセカンダリノードに同期されます。 プライマリノードでの書き込み操作が完了すると、oplog エントリが特別な local.oplog.rs コレクションに書き込まれます。 セカンダリノードは常にプライマリノードから新しい oplog エントリをインポートし、操作を適用します。

oplog のサイズが無制限に大きくなることを防止するために、local.oplog.rs は上限付きコレクションとして構成されています。 oplog データの量が指定しきい値に達すると、最も古いエントリが削除されます。 oplog の操作はすべて、べき等でなければなりません。 これにより、セカンダリノードに繰り返し適用されるかどうかにかかわらず、ある操作で得られる結果が同じであることが保証されます。

以下のコードブロックは、"ts"、"h"、"op"、"ns"、"o" などのフィールドを含むサンプルの oplog エントリです。

    {
      "ts" : Timestamp(1446011584, 2),
      "h" : NumberLong("1687359108795812092"), 
      "v" : 2, 
      "op" : "i", 
      "ns" : "test.nosql", 
      "o" : { "_id" : ObjectId("563062c0b085733f34ab4129"), "name" : "mongodb", "score" : "100" } 
    }
  • ts:操作が実行された時刻。 値には 2 つの数値が含まれます。 最初の値は UNIX タイムスタンプです。 2 番目の値は、1 秒以内に発生する各操作のシリアル番号を示すカウンターです。 カウンターは毎秒リセットされます。
  • h:操作の識別子
  • v:oplog のバージョン
  • op:操作のタイプ
  • i:挿入
  • u:更新
  • d:削除
  • c:createDatabase や dropDatabase などのコマンドを実行します
  • n:null。 この値は特別な目的で使用されます。
  • ns:操作が実行されるコレクション
  • o:操作の詳細。 このフィールドは、更新操作でのみ有効です。
  • o2:更新操作の条件。 このフィールドは、更新操作でのみ有効です。

初回の同期中に、セカンダリノードは initsync コマンドを実行して、プライマリノードまたは最新データを格納する別のセカンダリノードからのすべてのデータを同期します。 次に、セカンダリノードは tailable cursor 機能を引き続き使用して、プライマリノードの local.oplog.rs コレクション内の最新の oplog エントリを照会し、これらの oplog エントリの操作を適用します。

初回の同期プロセスには、以下の手順が含まれます。

  1. 時刻 T1 の前に、データ同期ツールはlistDatabaseslistCollectionsおよびcloneCollection コマンドを実行します。 時刻 T1 で、クラウドデータベース (local.oplog.rs データベースを除く) のすべてのデータが、プライマリノードからセカンダリノードへの同期を開始します。 時刻 T2 の時点で同期が完了したと仮定します。
  2. T1 から T2 までに生成された oplog エントリの操作はすべて、セカンダリノードに適用されます。 oplog エントリの操作はべき等です。 したがって、手順 1 で適用された操作を再適用できます。
  3. プライマリノードの各コレクションのインデックスに基づいて、対応するコレクションのインデックスがセカンダリノードで作成されます。 プライマリノードの各コレクションのインデックスは、手順 1 で作成されました。
    データベースのサイズとアプリケーションで書き込まれるデータの量に基づいて、oplog のサイズを設定する必要があります。 oplog が大きすぎると、ストレージスペースが無駄になる恐れがあります。 oplog サイズが小さすぎると、セカンダリノードが初期同期を完了できないことがあります。 たとえば、手順 1 で、データベースに大量のデータが格納されていて、oplog が十分に大きくない場合、oplog は T1 から T2 までに生成されたすべての oplog エントリを格納できないかもしれません。 その結果、セカンダリノードはプライマリノードのデータセットを完全には同期できません。

レプリカセットの変更

mongo シェルで replSetReconfig コマンドを実行するか、rs.reconfig() コマンドを実行することにより、レプリカセットを変更できます。 たとえば、メンバーの追加または削除、メンバーの優先度、投票、非表示および遅延属性の変更を実行できます。

たとえば、以下のコマンドを実行して、レプリカセットの第 2 メンバーの優先度を 2 に設定できます。

    cfg = rs.conf();
    cfg.members[1].priority = 2;
    rs.reconfig(cfg);

プライマリノードでの操作のロールバック

レプリカセットのプライマリノードに障害が発生したと仮定します。 以前のプライマリノードがレプリカセットに再参加した際に新しいプライマリノードで書き込み操作が実行された場合、以前のプライマリノードは他ノードに同期されていない操作をロールバックする必要があります。 こうすることで、以前のプライマリノードと新しいプライマリノードとの間のデータの整合性が保証されます。

以前のプライマリノードは、ロールバックデータを専用ディレクトリに書き込みます。 これにより、データベース管理者は mongorestore コマンドを実行して、必要であれば、操作を復元できます。

レプリカセットの読み取り/書き込み設定

  • 読み取り優先度

    デフォルトでは、レプリカセットの読み取りリクエストはすべて、プライマリノードまでルーティングされます。 ただし、ドライバーの読み取り優先度モードを変更して、読み取りリクエストを他ノードまでルーティングすることができます。

    • primary: デフォルトモードです。 読み取りリクエストはすべて、プライマリノードまでルーティングされます。
    • primaryPreferred: 読み取りリクエストは、プライマリノードへと優先的にルーティングされます。 プライマリノードを使用できない場合、読み取りリクエストはセカンダリノードにルーティングされます。
    • secondary: 読み取りリクエストはすべて、セカンダリノードにルーティングされます。
    • secondaryPreferred: 読み取りリクエストは、優先的にセカンダリノードへとルーティングされます。 使用できるセカンダリノードがない場合、読み取りリクエストはプライマリノードにルーティングされます。
    • nearest: 読み取りリクエストは、ping コマンドを実行することで検出可能な、最も近くに到達可能なノードへとルーティングされます。
  • Write Concern

    デフォルトでは、プライマリノードは、データがプライマリノードに書き込まれた後、書き込み操作に成功したことを示すメッセージを返します。 ドライバに wite concern を設定して、書き込み操作を成功させるためのルールを指定できます。 詳細については、「Write Concern」をご参照ください。

    以下の write concern は、データがタイムアウト前にノードの大部分に書き込まれた後にのみ書き込み操作が成功することを示しています。 タイムアウト期間は 5 秒です。

        db.products.insert(
          { item: "envelopes", qty : 100, type: "Clasp" },
          { writeConcern: { w: majority, wtimeout: 5000 } }
        )

    上記の設定は、各リクエストに適用されます。 レプリカセットのデフォルトの write concern を変更することもできます。 レプリカセットの write concern は、レプリカセットに対するすべてのリクエストに適用されます。

        cfg = rs.conf()
        cfg.settings = {}
        cfg.settings.getLastErrorDefaults = { w: "majority", wtimeout: 5000 }
        rs.reconfig(cfg)