collectionごとのメモリ利用量(MongoDB)

一般的なDBのテーブルのことをMongoDBではコレクションと呼びます。 下のグラフはコレクションごとのキャッシュメモリの利用状況(1週間分)です。毎日同じ時間に特定のコレクションのメモリ使用率が増えていることがわかります。キャッシュメモリの上限は一定なので、どこかで使う量が増えればほかのコレクションが使える量が減ります。

f:id:motobrew:20201117100428p:plain

コレクションごとのメトリクスを可視化する理由

  • シャーディングするコレクションを選定する
  • 異常をより早く検知して大規模な障害を未然に防ぐ

突然ですが質問です。みなさんは新しいプロダクトを作ったり、プロダクトが成長する過程で、システムの拡張性(スケーラビリティ)について悩んだことはありませんか?

オンライン教育の浸透によりスタディサプリ&Quipperの利用者は右肩上がりで増えており、将来に対するスケーラビリティは優先度の高い課題です。

Quipperでは一部のコレクションのみをMongoDB Atlasでシャーディングして運用しています。一方で、大半のコレクションはシャーディングしておらず、将来のユーザー増加に耐えるためには、負荷の原因となるコレクションを見極める必要があります。どのコレクションの負荷が高いかを把握したり、障害を未然に防ぐ目的でこのようなグラフやアラートを運用しています。

どのように可視化しているのか

Collection-based Metrics

mongo-clidb.<collection名>.stats() を実行するとコレクションごとのメトリクスを取得することができます。それをDatadogにカスタムメトリクスとして送信します。今後発生するかもしれない障害にはどんなメトリクスが役立つかはわからないので、キャッシュ周りのメトリクスなどをまとめて送っています。stats()で取得できるメトリクスは100種類以上あり、その中で30ぐらいに絞っています。

f:id:motobrew:20201117184306p:plain

量とカウンタの2種類のメトリクス

可視化する指標の例

  • wiredTiger -> cache -> bytes currently in the cache
  • wiredTiger -> transactions -> update conflicts

メトリクスには2種類あって、bytes currently in the cache などは取得した時点のサイズ(量)を表しておりそのままグラフにすれば推移がわかります。一方で update conflicts などはmongodを再起動した時点で0にリセットされて、その後イベントが発生するごとにカウンタが増えていくので、可視化する場合は2回取得した増分を表示させます。

カウンタの例
10:00 10121(最後にmongodを起動してから10121回発生)
10:05 10125 (過去5分で4回発生)
10:10 10125 (過去5分で0回発生)
10:15 10126 (過去5分で1回発生)

f:id:motobrew:20201117165455p:plain Datadogで可視化する際にtimeshift()で前回の値との差分を表示させています。

SaaSで見られるメトリクスと見られないメトリクス

基本的なコレクションごとのメトリクスはMongoDB AtlasやCloud Managerの管理画面でも見ることができます

  • データ量
  • ドキュメント数
  • インデックスサイズ

一方で、キャッシュメモリの使用量など詳細なコレクションごとのメトリクスを見るためには db.collection.stats() を実行する必要があります。

得られるメリット

以下のような情報を得ることができます

  1. どのコレクションが多くのメモリを使っているか
  2. update conflicts がどのコレクションでいつ発生しているか
  3. 1時間以内の詳細な動きや定期的な変化
  4. 普段と違う動きの検出(アラートの作成)

MongoDBはそのユースケースによってメモリサイズがボトルネックになる場合とI/Oがボトルネックになる場合があるようです。

例えばコレクションの数が全部で200以上ある中で上位3つのコレクションで50%以上のメモリを使っていることがわかったりします。その場合はそのコレクションを別のレプリカセットに切り出したりすればキャッシュに余裕ができます。(垂直分割)

update conflicts が大量に発生している場合は、そのコレクションをシャーディングしたり(水平分割)、I/O性能のより高い構成にスケールアップする必要があります。

もしこういった情報が知りたいなら、stats()を実行してjqやgrepで分析すれば、a.とb.に関してはとりあえず結果を得ることはできます。

失敗?今の状態ができるまで

Proof of Concept(試作)
  • db.collection.stats() の結果をjqでCSVに変換してS3に保管
  • 任意のCSVを手動でダウンロードしてスプレッドシートで分析
  • DB負荷を懸念して1日2回だけすべてのコレクションを対象に実行
自動化&可視化
  • 特定のコレクションに対して、db.collection.stats() を実行してDatadogに送信
  • DB負荷を懸念して利用率が上位にあるコレクションを指定して可視化
現在のバージョン
  • 全コレクションのメトリクスを5分おきに取得
  • 負荷がかからないように1回の接続で全コレクションのstats()を実行

はじめにPoCを作って実現可能性を確認しました。S3にアップロードしたJSONに対してAthenaで分析できるのではと考えて試しましたが、コレクションごとにインデックスが異なるためJSONのフォーマットも変わることがわかってAthenaはやめました。

手作業や半自動化でまず結果を確認して役立ちそうだとわかったので完全に自動化しました。負荷が上がらないように1回の接続で情報を取得するように手を加えたことで、現在ではすべてのコレクションのデータを5分おきに取得できるようになりました。

さいごに

この仕組みはまだ開始したばかりなので、これを活用して拡張性や信頼性を高めるのはこれからが本番です。2020年、Quipperの新たな挑戦が始まっています。

日本、そして世界のオンライン教育を支えるSREの仲間を募集しています!

深尾もとのぶ