MongoDBを使用してアプリケーションを開発する場合、優れたデータベースパフォーマンスが重要です。場合によっては、次のようないくつかの理由により、データ提供プロセス全体が低下することがあります。
- 不適切なスキーマデザインパターン
- インデックス作成戦略の不適切な使用または使用なし
- 不十分なハードウェア
- レプリケーションの遅れ
- クエリ手法のパフォーマンスが低い
これらの挫折の中には、ハードウェアリソースを増やすことを余儀なくされるものもあれば、そうでないものもあります。たとえば、クエリ構造が不十分だと、クエリの処理に時間がかかり、レプリカの遅延が発生したり、データが失われたりする可能性があります。この場合、おそらくストレージメモリが十分ではなく、おそらくスケールアップが必要であると考えるかもしれません。この記事では、MongoDBデータベースのパフォーマンスを向上させるために採用できる最も適切な手順について説明します。
スキーマデザイン
基本的に、最も一般的に使用される2つのスキーマ関係は...
- 1対数
- 1対多
最も効率的なスキーマ設計は1対多の関係ですが、それぞれに独自のメリットと制限があります。
1対数
この場合、特定のフィールドには埋め込みドキュメントがありますが、オブジェクトIDでインデックス付けされていません。
簡単な例を次に示します。
{
userName: "Brian Henry",
Email : "[email protected]",
grades: [
{subject: ‘Mathematics’, grade: ‘A’},
{subject: English, grade: ‘B’},
]
}
この関係を使用する利点の1つは、1回のクエリで埋め込みドキュメントを取得できることです。ただし、クエリの観点からは、単一の埋め込みドキュメントにアクセスすることはできません。したがって、埋め込まれたドキュメントを個別に参照しない場合は、このスキーマ設計を使用するのが最適です。
1対多
この関係では、あるデータベースのデータは別のデータベースのデータに関連付けられています。たとえば、ユーザー用のデータベースと投稿用のデータベースを作成できます。したがって、ユーザーが投稿を行うと、ユーザーIDで記録されます。
ユーザースキーマ
{
Full_name: “John Doh”,
User_id: 1518787459607.0
}
投稿スキーマ
{
"_id" : ObjectId("5aa136f0789cf124388c1955"),
"postTime" : "16:13",
"postDate" : "8/3/2018",
"postOwnerNames" : "John Doh",
"postOwner" : 1518787459607.0,
"postId" : "1520514800139"
}
このスキーマ設計の利点は、ドキュメントがスタンドアロンと見なされることです(個別に選択できます)。もう1つの利点は、この設計により、さまざまなIDのユーザーがpostsスキーマ(したがってOne-to-Manyという名前)からの情報を共有できることです。基本的にテーブル結合を使用せずに、「N-to-N」スキーマにすることもできます。このスキーマ設計の制限は、2番目のコレクションのデータをフェッチまたは選択するために少なくとも2つのクエリを実行する必要があることです。
したがって、データをモデル化する方法は、アプリケーションのアクセスパターンによって異なります。これに加えて、上記で説明したスキーマ設計を検討する必要があります。
スキーマ設計の最適化手法
-
特定のデータセットに対して実行する必要のあるクエリの数を減らすために、ドキュメントの埋め込みを可能な限り採用します。
-
頻繁に更新されるドキュメントには非正規化を使用しないでください。フィールドが頻繁に更新される場合は、更新が必要なすべてのインスタンスを見つけるタスクがあります。これにより、クエリ処理が遅くなり、非正規化に関連するメリットさえも圧倒されます。
-
ドキュメントを個別にフェッチする必要がある場合は、集約パイプラインなどの複雑なクエリの実行に時間がかかるため、埋め込みを使用する必要はありません。
-
埋め込むドキュメントの配列が十分に大きい場合は、それらを埋め込まないでください。アレイの拡張には、少なくとも制限があります。
適切なインデックス作成
これはパフォーマンスチューニングのより重要な部分であり、アプリケーションクエリ、読み取りと書き込みの比率、およびシステムにある空きメモリの量を包括的に理解する必要があります。インデックスを使用する場合、クエリはコレクションではなくインデックスをスキャンします。
優れたインデックスとは、クエリによってスキャンされたすべてのフィールドを含むインデックスです。これは複合インデックスと呼ばれます。
フィールドの単一のインデックスを作成するには、次のコードを使用できます:
db.collection.createIndex({“fields”: 1})
複合インデックスの場合、インデックスを作成するには:
db.collection.createIndex({“filed1”: 1, “field2”: 1})
インデックスを使用したクエリの高速化に加えて、並べ替え、サンプル、制限などの他の操作の利点もあります。たとえば、スキーマを{f:1、m:1}として設計した場合、find as
以外の追加の操作を実行できます。db.collection.find( {f: 1} ).sort( {m: 1} )
RAMからデータを読み取る方が、ディスクから同じデータを読み取るよりも効率的です。このため、インデックスがRAMに完全に収まるようにすることを常にお勧めします。コレクションの現在のindexSizeを取得するには、次のコマンドを実行します:
db.collection.totalIndexSize()
36864バイトのような値を取得します。サーバーのワーキングセット全体のニーズに対応する必要があるため、この値はRAMサイズ全体の大きな割合を占めるべきではありません。
効率的なクエリは、選択性も強化する必要があります。選択性は、インデックスを使用して結果を絞り込むクエリの機能として定義できます。より割線にするために、クエリはインデックス付きフィールドを持つ可能なドキュメントの数を制限する必要があります。選択性は主に、低選択性フィールドと別のフィールドを含む複合インデックスに関連付けられています。たとえば、このデータがある場合:
{ _id: ObjectId(), a: 6, b: "no", c: 45 }
{ _id: ObjectId(), a: 7, b: "gh", c: 28 }
{ _id: ObjectId(), a: 7, b: "cd", c: 58 }
{ _id: ObjectId(), a: 8, b: "kt", c: 33 }
クエリ{a:7、b:“ cd”}は、2つのドキュメントをスキャンして、一致する1つのドキュメントを返します。ただし、値aのデータが均等に分散されている場合、つまり
{ _id: ObjectId(), a: 6, b: "no", c: 45 }
{ _id: ObjectId(), a: 7, b: "gh", c: 28 }
{ _id: ObjectId(), a: 8, b: "cd", c: 58 }
{ _id: ObjectId(), a: 9, b: "kt", c: 33 }
クエリ{a:7、b:“ cd”}は、1つのドキュメントをスキャンして、このドキュメントを返します。したがって、これには最初のデータ構造よりも短い時間がかかります。
データベースインフラストラクチャ全体のClusterControlSingleコンソールClusterControlのその他の新機能を確認するClusterControlを無料でインストールリソースのプロビジョニング
不十分なストレージメモリ、RAM、およびその他の動作パラメータは、MongoDBのパフォーマンスを大幅に低下させる可能性があります。たとえば、ユーザー接続の数が非常に多い場合、サーバーアプリケーションが要求をタイムリーに処理する機能が妨げられます。 MongoDBで監視する重要事項で説明したように、使用している限られたリソースの概要と、仕様に合わせてそれらをスケーリングする方法を確認できます。多数の同時アプリケーション要求の場合、データベースシステムは需要に追いつくのに圧倒されます。
レプリケーションラグ
データベースから一部のデータが欠落していることに気付く場合や、何かを削除すると、そのデータが再び表示される場合があります。適切に設計されたスキーマ、適切なインデックス作成、および十分なリソースがあれば、最初はアプリケーションは問題なくスムーズに実行されますが、ある時点で、後者の問題に気付くでしょう。 MongoDBは、いくつかの設計基準を満たすためにデータが冗長的にコピーされるレプリケーションの概念に依存しています。これに関する仮定は、プロセスが瞬間的であるということです。ただし、ネットワーク障害や未処理のエラーが原因で、遅延が発生する場合があります。一言で言えば、操作がプライマリノードで処理される時間とそれがセカンダリノードで適用される時間の間に大きなギャップがあります。
レプリカラグによるセットバック
-
一貫性のないデータ。これは特に、セカンダリに分散される読み取り操作に関連しています。
-
ラグギャップが十分に広い場合、複製されていないデータの多くがプライマリノードにある可能性があり、セカンダリノードで調整する必要があります。ある時点で、特にプライマリノードを回復できない場合、これは不可能になる可能性があります。
-
プライマリノードのリカバリに失敗すると、最新ではないデータを使用してノードを実行するように強制される可能性があり、その結果、プライマリをリカバリさせるためにデータベース全体が削除される可能性があります。
セカンダリノード障害の原因
-
CPU、ディスクIOPS、ネットワークI / Oの仕様に関して、セカンダリよりもプライマリパワーが優れています。
-
複雑な書き込み操作。たとえば、
のようなコマンドdb.collection.update( { a: 7} , {$set: {m: 4} }, {multi: true} )
プライマリノードは、この操作を十分な速さでoplogに記録します。ただし、セカンダリノードの場合、idなどのいくつかの基準仕様を満たすために、これらのopsをフェッチし、インデックスとデータページをRAMに読み込む必要があります。プライマリノードが操作を実行するレートを維持するためにこれを十分に高速に実行する必要があるため、操作の数が十分に多い場合は、予想される遅延が発生します。
-
バックアップを作成するときのセカンダリのロック。この場合、プライマリを無効にするのを忘れる可能性があるため、通常どおり操作を続行します。ロックが解除される時点では、特に大量のデータバックアップを処理する場合、レプリケーションラグに大きなギャップが生じます。
-
インデックス作成。インデックスがセカンダリノードに蓄積されると、それに関連する他のすべての操作がブロックされます。インデックスが長時間実行されている場合、レプリケーションラグの一時的な中断が発生します。
-
接続されていないセカンダリ。ネットワークの切断が原因でセカンダリノードに障害が発生することがあります。これにより、再接続時にレプリケーションの遅延が発生します。
レプリケーションラグを最小化する方法
-
_idフィールドを持つコレクション以外に一意のインデックスを使用します。これは、レプリケーションプロセスが完全に失敗するのを防ぐためです。
-
ポイントインタイムやファイルシステムのスナップショットなど、必ずしもロックを必要としない他の種類のバックアップを検討してください。
-
バックグラウンドブロッキング操作が発生するため、大きなインデックスを作成しないでください。
-
セカンダリを十分に強力にします。書き込み操作が軽量の場合、電力不足のセカンダリを使用すると経済的です。ただし、書き込み負荷が大きい場合、セカンダリノードがプライマリより遅れる可能性があります。より確実にするには、セカンダリは、プライマリノードとのレートを維持するために、oplogの読み取りを十分に高速化するのに十分な帯域幅を備えている必要があります。
効率的なクエリ手法
上記のようにインデックス付きクエリを作成してクエリ選択性を使用する以外に、クエリを固定して効果的にするために使用できる他の概念があります。
クエリの最適化
-
カバーされたクエリを使用します。対象となるクエリは、インデックスによって常に完全に満たされるクエリであるため、ドキュメントを調べる必要はありません。したがって、対象となるクエリにはインデックスの一部としてすべてのフィールドが含まれている必要があり、その結果、結果にはこれらすべてのフィールドが含まれている必要があります。
この例を考えてみましょう:
{_id: 1, product: { price: 50 }
このコレクションのインデックスを次のように作成すると
{“product.price”: 1}
検索操作を考慮すると、このインデックスはこのクエリをカバーします;
db.collection.find( {“product.price”: 50}, {“product.price”: 1, _id: 0} )
product.priceフィールドと値のみを返します。
-
埋め込まれたドキュメントの場合は、ドット表記(。)を使用します。ドット表記は、配列の要素や埋め込みドキュメントのフィールドにアクセスするのに役立ちます。
配列へのアクセス:
{ prices: [12, 40, 100, 50, 40] }
たとえば、4番目の要素を指定するには、次のコマンドを記述できます。
“prices.3”
オブジェクト配列へのアクセス:
{ vehicles: [{name: toyota, quantity: 50}, {name: bmw, quantity: 100}, {name: subaru, quantity: 300} }
Vehicles配列の名前フィールドを指定するには、このコマンドを使用できます
“vehicles.name”
-
クエリがカバーされているかどうかを確認します。これを行うには、db.collection.explain()を使用します。この関数は、他の操作の実行に関する情報を提供します。 db.collection.explain()。aggregate()。 Explain関数の詳細については、explain()を確認してください。
一般に、クエリに関する限り、最高の手法はインデックスを使用することです。インデックスのみをクエリする方が、インデックス外のドキュメントをクエリするよりもはるかに高速です。それらはメモリに収まるため、ディスクではなくRAMで利用できます。これにより、メモリからそれらをフェッチするのに十分なほど簡単かつ高速になります。