見出し画像

MODE における MongoDB のレプリカとバックアップの運用

執筆者:Tomoyuki Matsushita, Software Engineer at MODE

日本国内では RDBMS を採用している企業が多いのではないかと思いますが、MODE では MongoDB をバックエンドデータストアとして用いています。
MongoDB はスキーマレスであることも特徴的ではあるのですが、組み込みのクラスタリング機能が充実している点も見過ごせません。

具体的にはまず高可用性があげられます。
プライマリノードがダウンした場合は、選出アルゴリズムに基づいて生存しているノードの中から新たなプライマリが自動的に選出および昇格されます。
MongoDB はこうしたフェイルオーバを自動的に行ないます。

もう1つの特徴は読み込みの分散です。
RDBMS でも書き込みはプライマリノードに行いつつつも、読み込みはレプリカノードに分散させる構成を取ることがよくあります。
MongoDB においてはサーバとドライバの協調によりこのようなアクセスを透過的に行うことが可能となっています。
さらに加えて、この手の分散で起こりがちな「書き込みした内容が読み込めない」といった問題に対して一貫性の度合いを設定することも可能となっています。

また sharding 機能もビルトインで用意されており、書き込みの分散もできるようになっています。

本稿では、そんな MongoDB を我々がどのように運用しているのかレプリカとバックアップの観点から解説していきます。

MODE における MongoDB クラスタ運用の課題

本番環境における安全なオペレーション

これは MongoDB に限った話ではありませんが、本番環境のデータストアに対して調査用のクエリを発行するなどのオペレーションを行いたくなるケースがあります。
しかしながら、例えばうっかり CPU 負荷の高いクエリを発行してしまうなどで本番環境に影響を与えてしまってはインシデントに繋がりかねません。

新規ノード追加時のレプリケーション負荷

MODE では MongoDB をメインのデータストアとして使っていることを冒頭に述べましたが、時系列データベースもその例外ではなく、大量の時系列データを格納しています。しかしその結果、新規メンバを素朴にクラスタに追加してしまうといつまで経っても initial sync と呼ばれる新規メンバへの初期データ同期が終わらない上、プライマリノードの CPU 使用率に影響が出るという問題に直面しました。

Non-Voting Hidden メンバによる安全なオペレーションの実現

アプリケーションに影響を与えることなく本番環境の MongoDB のデータを読み取ることは可能で、我々は Non-Voting Hidden メンバというちょっと変わったレプリカを作ることで実現しています。このセクションではこれがどういったものかを解説していきます。

MongoDB クラスタにおいては、メンバ (Nodeではなく Member と公式が表記しているので以下メンバで呼び方を統一します) の属性を設定することができます。

MODE では本番環境用のメンバとは別に "Non-Voting" かつ "Hidden" という属性を持ったメンバを用意しています。
企業によっては本番の RDBMS に調査用のレプリカノードを作成しているケースがあると思いますが、それと同等の役割を持たせたものとなっています。

まず Non-Voting について解説をします。
前述の通り、MongoDB ではプライマリのダウン時には新たなプライマリが選出され、この選出は生存しているメンバの投票によって決定されます。
しかし Non-Voting の設定をしている場合、そのメンバは投票権を持たなくなります。
またこの設定をされたメンバは他メンバから投票されることもなくなり、プライマリへと昇格することもなくなります。
つまりフェイルオーバに一切寄与しないメンバとして振る舞うことになります。

続いて Hidden について解説をします。
MongoDB のクライアント (ドライバ) はクラスタ内のメンバに対し、プライマリかセカンダリかを判断した上で分散するようにリクエストします (この分散の方針にも設定があります) 。
しかし Hidden の設定をしたメンバは、ドライバから完全に無視されることになりアクセスされることがなくなります。

したがって Non-Voting かつ Hidden なメンバは本番環境アプリケーションに影響を与えることがありません。
これを用いることで、調査を目的として安全に本番環境のデータにアクセスすることが可能となります。

EBS スナップショットを用いたバックアップと initial sync 負荷回避

前述の通り、MODE の場合、愚直に新規メンバを既存クラスタに追加してしまうと initial sync (初期データ同期) の負荷の問題が出てしまいます。そこで MODE では EBS スナップショットを活用してこの問題を回避しています。

バックアップ機構について

メンバ追加の前に、まずバックアップ機構について解説をします。こちらを応用して我々は initial sync の問題を回避しているためです。

キャッシュなどの失ってもいい内容でもなければ、本番環境のデータバックアップは必須と言えるでしょう。MODE においては、本番環境 MongoDB の EBS スナップショットを定期的に取るというバックアップ手法を採用しています。このスナップショットは前述の Non-Voting Hidden メンバに対して取るものとしており、バックアップのスクリプトが本番環境に影響を与えないようにしています。

新規メンバ追加へのバックアップの応用

こうして定期的に取得しているバックアップを、新規メンバ追加に応用することで initial sync の負荷を回避しています。オペレーションは以下のようになります。

  1. 前述のスナップショットからボリュームを作成する

  2. 1 をアタッチした EC2 インスタンスを作成し、MongoDB サーバとする

  3. 2 を既存の MongoDB クラスタへ追加する

こうすることで新たなメンバはデータの大部分を保持した状態でクラスタに参加することになります。したがってバックアップ時点から最新時点の差分だけレプリケーションで埋めれば済むようになるため、大幅にレプリケーション時の負荷が下がると同時に時間短縮ができ、メンバの追加が可能となります。

EBS スナップショットの活用における注意点と対策

上記のように MODE では EBS スナップショットを活用してバックアップ兼、新規メンバ追加時の負荷回避に用いていますが、気をつけるべきことがいくつかあります。

まず、MODE においては EBS スナップショットを取る前に db.fsyncLock() コマンドを実行する必要があります。
このコマンドはペンディング中の書き込み操作内容をディスクにフラッシュするとともに、書き込み操作を受け付けなくします (よってアプリケーションからアクセスの行くメンバに対して行えません) 。
この操作は以下の条件を満たしていれば不要なのですが、MODE では RAID0 かつ journal file を別のボリュームに格納しています。

  1. MongoDB では journaling を有効にしている

  2. Journal file をデータを同一のボリュームに格納している

  3. RAID0 または RAID10 構成にしていない

EBS スナップショットを取ったら db.fsyncUnlock() により再び書き込み操作を可能とするわけですが、このロックを取っている間はレプリケーションによる書き込みすら受け付けなくなります。
MODE においてはレプリケーション遅延に対してアラートを設定しているため、設定によってはバックアップを取る度にアラートが飛んでしまうことになります。
幸いこのバックアップは前述の通り、本番環境への影響がない Non-Voting Hidden メンバで行っているため、そこだけレプリケーション遅延のアラートを大目に見ることで不要なアラートを抑止しています。

AWS の仕様に起因する課題としては EBS の初期化があげられます。
EBS スナップショットからボリュームを復元して EC2 インスタンスにマウントした場合、初回アクセスしたブロックはアクセスが著しく遅くなります。これは、未アクセスの時点ではデータの実体が S3 上にあり、初めてアクセスした時点それをダウンロードしてボリュームに書き込む仕組みとなっているためであり、この処理が初期化と呼ばれています。
これでは MODE のワークロードに耐えることができないため、我々はFSR (Fast Snapshot Restore) を用いてこの初期化のコストを回避しています。
しかし残念ながら FSR は料金が非常に高いという別の注意点があり、うっかり消さずに残してしまうとコストが問題になってしまいます。そこで MODE では、長期間有効となっている FSR を無効化する Lambda function を作り EventBridge で定期実行させることで、この問題を防止しています。

まとめ

本稿では MODE が MongoDB を主に用いていることと MongoDB の持つクラスタリング機能の概観から始まり、安全なオペレーションと大規模利用時のメンバ追加における課題について、どのように取り組んでいるかを解説いたしました。我々は Non-Voting Hidden メンバと EBS スナップショットを活用することでそれらの課題を解決しています。