MongoDBでゆるふわDB体験

第13回MongoDBの性能

はじめに

前回前々回とMongoDBの非機能面に着目してきましたが、今回も非機能に着目して、MongoDBの処理性能について説明します。まず前半では、MongoDBの動作の中で処理性能に影響を与える動作を説明します。それをふまえた上で、後半では処理性能をどのように上げるかを説明します。

なお、今回はMongoDBを単体で使った場合の処理性能を考えています。レプリケーションやシャーディングを行った状態の処理性能については対象外とさせていただきます。

処理性能にかかわるMongoDBの動作

データアクセス

MongoDBがどのようにデータにアクセスするかは、MongoDBの処理性能を考える上で非常に重要です。

MongoDBではデータファイルを仮想メモリにマッピング(Linuxではmmapという機能を利用)し、MongoDBのプロセスは仮想メモリにアクセスします。図1のように、アクセスするデータが物理メモリに乗っている場合には高速にアクセスできますが、アクセスするデータが物理メモリに乗っていない場合(ページフォルト発生時)は、ディスクからデータを物理メモリに読み込み、その後アクセスする必要があります。そのため、アクセスするデータ(インデックスを含む)がメモリ上にあるかどうかが処理性能を大きく左右します。

図1 データの場所によるアクセス速度の違い
図1 データの場所によるアクセス速度の違い

インデックス

キーの値からドキュメントを検索する場合、インデックスが張っていないと全てのドキュメントを順次に探す(テーブルスキャンする)ことになります。これは大変コストのかかるオペレーションです。一方インデックスを適切に張っていれば、キーの値からインデックスを参照しデータがどこにあるかを特定することができます。そのため、適切なインデックスを張り、テーブルスキャンをしないようにすることが処理性能向上につながります。

ロック

MongoDBにアクセスするクライアントが複数ある場合、複数クライアントが1つのデータに同時に書き込まないようにロックが発生します。バージョン2.2からはデータベース単位でロックが発生し、複数クライアントで読むことはできますが、書き込んでいるクライアントがあると他のクライアントは読み込めません(ロック解放待ち⁠⁠。ロック解放待ちが発生すると処理性能は劣化します。

ジャーナル

ジャーナルはMongoDBでデータの保全性を高めるための仕組みです。この機能を有効にすると(64bitシステムではデフォルトで有効⁠⁠、MongoDBはまずオペレーションをジャーナルファイルに書き込んだ後に、データへのオペレーションを行います。この処理には必ずディスクアクセスが発生するため、処理性能が劣化します。

ドキュメントの再配置

MongoDBはスキーマレスであるため、ドキュメントのサイズはドキュメントを挿入するまでわかりません。そのため、MongoDBはデータファイルに順番にドキュメントを格納していきます。しかし更新などでドキュメントのサイズが大きくなった場合、今まであった位置にはドキュメントが入らないため、ドキュメントをコレクションの最後尾に再配置します。この再配置は余計なオペレーションを生み、加えてインデックスの張り直しが必要になることがあるため、書き込み性能の劣化につながります。

図2 更新によりドキュメントの再配置が発生する場合
図2 更新によりドキュメントの再配置が発生する場合

MongoDBの処理性能を上げる6か条

では、今まで説明したMongoDBの動作を考慮して、MongoDBの性能を上げる6つのポイントを説明していきます。

1.メモリは多く積む

ハードウェアの選定ではメモリが一番重要です。理想はインデックスとデータが全て乗るメモリを用意する事です。そうすればディスクへのアクセスは最小限になり、オンメモリデータベース並みの処理速度が出せます。また、データが運用中に増えるようなシステムでは、データやインデックスの増加を見越して、メモリを多めに積んでおくことがよいでしょう。

2.ディスクは速いものを使う

ハードウェアの選定では、ディスクはメモリの次に重要です。特にジャーナルを利用する環境やメモリが少ない環境であれば、ディスクへの書き込みが頻繁に発生するため、ディスクの速さがより重要になります。逆にCPUの処理性能やネットワークの処理性能はそれほど問題になることはありません。

3.インデックスが効くようにする

データベースの設計になりますが、できるだけインデックスが効くようにインデックスを作成しましょう。具体的には、クエリがどのキーで検索することが多いかを解析し、そのキーに対してインデックスを張りましょう。また、思い切って「--notablescan」オプションを指定して、インデックスを使わないクエリを許可しないように設定するのも有効です。

4.ロック解放待ちをしないようにする

アプリケーションの設計になりますが、ロックを長時間取得するようなクエリは、裏で動作している他のクエリに影響を与えないように、タイミングを変更するなどの工夫をすることが重要です。

5.ドキュメントに必要なフィールドをあらかじめ作っておく

ドキュメント肥大化によりドキュメントの再配置が起こらないように、ドキュメントに必要なフィールドをあらかじめ作っておくことが重要です。具体的には以下の例のように、ドキュメントの新規作成時に必要なフィールド(以下の例では"tel")を作っておきます。

> db.coll.insert({"name":"taro"})
> db.coll.update({"name":"taro"},{ $set : {"tel":"090-1234-5678"}})
→updateでドキュメントが肥大化する

> db.coll.insert({"name":"taro", "tel":"xxx-xxxx-xxxx"})  # ←あらかじめ"tel"を作っておく
> db.coll.update({"name":"taro"},{ $set : {"tel":"090-1234-5678"}})
→updateでドキュメントが肥大化しない

6.ジャーナルの書き込み回数を減らす

ジャーナルは必ずディスクアクセスが発生しますので、⁠--journalCommitInterval」オプションを指定して書き込みの間隔を広げましょう。もしくは思い切って「--nojournal」オプションでジャーナルを止めてしまう事もできます。しかし、ジャーナルの機能を停止すると突然のサーバ停止時にデータがロストする可能性があるため、データの重要性を鑑みて適切な値を設定しましょう。

まとめと次回のテーマ

今回はMongoDBの処理性能にかかわる動作の説明と、処理性能向上6か条を紹介しました。しかし、今回紹介したのは単体構成のみの話で、MongoDBの真骨頂であるシャーディングによる分散処理については触れることができませんでした。MongoDBはシャーディングをしっかり組めば、驚くような処理性能を発揮できます。本記事をベースにして、皆さんも超高性能なシャーディングの構築にチャレンジしてみてください。

次回はいよいよ最終回、MongoDBの日本のコミュニティ、MongoDB JPの活動について紹介します。お楽しみに!

おすすめ記事

記事・ニュース一覧