本番環境では、アプリケーションは、アプリケーションとのユーザーインタラクションを改善する目的で、ユーザーにタイムリーな応答を提供する必要があります。ただし、データベースクエリが遅れ始め、応答がユーザーに到達するまでの待ち時間が長くなるか、設定された平均タイムアウトを超えたためにスループット操作が終了する場合があります。
このブログでは、MongoDBでこれらの問題を特定する方法、問題が発生したときに修正する方法、およびこれが二度と起こらないようにするための可能な戦略について学習します。
多くの場合、クエリ応答が遅くなる原因は、基盤となるワーキングセットに耐えられないCPU容量の低下です。この場合のワーキングセットは、スループットインスタンスの対象となるデータとインデックスの量であり、その時点でアクティブになります。これは、関連するデータの量が時間の経過とともに増加し、プラットフォームを使用するユーザーの数が増えると予想される場合のキャパシティプランニングで特に考慮されます。
MongoDBで遅いクエリを識別する方法は2つあります。
- db.currentOp()ヘルパーの使用
MongoDBプロファイラーの使用
MongoDBのデータベースプロファイラーは、実行中のmongodインスタンスに対して実行されるデータベースコマンドに関する詳細情報を収集するためのメカニズムです。つまり、スループット操作(作成、読み取り、更新、削除)と構成および管理コマンドです。
プロファイラーは、system.profileという名前の上限付きコレクションを利用して、すべてのデータを書き込みます。つまり、コレクションのサイズがいっぱいになると、新しいデータ用のスペースを確保するために古いドキュメントが削除されます。
プロファイラーはデフォルトでオフになっていますが、プロファイリングレベルに応じて、データベースごとまたはインスタンスごとに有効にできます。可能なプロファイリングレベルは次のとおりです。
- 0-プロファイラーがオフになっているため、データを収集しません。
- 1-プロファイラーはslowmsの値よりも時間がかかる操作のデータを収集します
- 2-プロファイラーはすべての操作のデータを収集します。
ただし、プロファイリングを有効にすると、特にプロファイリングレベルが2に設定されている場合に、データベースとディスクの使用量にパフォーマンスの影響が生じます。本番デプロイメントでプロファイラーを有効にして構成する前に、パフォーマンスへの影響を考慮する必要があります。
プロファイリングを設定するには、次のようなdb.setProfilingLevel()ヘルパーを使用します。
db.setProfilingLevel(2)
system.profileコレクションに保存されるサンプルドキュメントは次のとおりです。
{ "was" : 0, "slowms" : 100, "sampleRate" : 1.0, "ok" : 1 }
「ok」:1のキーと値のペアは、操作が成功したことを示しますが、slowmsは操作に必要なミリ秒単位のしきい値時間であり、デフォルトでは100msです。
db.setProfilingLevel(1, { slowms: 50 })
system.profileコレクションに対してデータをクエリするには、次のコマンドを実行します。
db.system.profile.find().pretty()
db.currentOp()helperの使用
この関数は、現在実行中のクエリを、実行時間などの非常に詳細な情報とともに一覧表示します。実行中のmongoシェルで、次のようにコメントを実行します:
db.currentOp({“ secs_running”:{$ gte:5}})
ここで、secs_runningはフィルタリング戦略であり、実行に5秒以上かかった操作のみが返され、出力が減少します。これは、データベースに影響を与える可能性のあるパフォーマンスへの悪影響により、CPUの状態を100%と評価できる場合によく使用されます。したがって、値を変更することで、実行に時間がかかるクエリを知ることができます。
返送されたドキュメントには、対象となるキーとして次のものがあります。
- クエリ :クエリの内容
- アクティブ :クエリがまだ進行中の場合。
- ns :クエリが実行されるコレクション名
- secs_running :クエリがこれまでにかかった時間(秒単位)
どのクエリに時間がかかっているかを強調表示することで、CPUに過負荷をかけているものを特定しました。
上記で説明したように、クエリのレイテンシは関連するデータの量に大きく依存します。そうしないと、実行計画が非効率になります。つまり、たとえば、コレクションでインデックスを使用せず、特定のレコードを更新する場合、操作では、クエリ仕様に一致するドキュメントのみをフィルタリングするのではなく、すべてのドキュメントを処理する必要があります。論理的には、これには時間がかかるため、クエリが遅くなります。クエリのパフォーマンスに関する統計情報を提供するexplain(‘executionStats’)を実行すると、非効率的な実行プランを調べることができます。この時点から、インデックスが最適であるかどうかの手がかりを提供するだけでなく、クエリがインデックスをどのように利用しているかを知ることができます。
{
"queryPlanner" : {
"plannerVersion" : 1,
...
"winningPlan" : {
"stage" : "COLLSCAN",
...
}
},
"executionStats" : {
"executionSuccess" : true,
"nReturned" : 3,
"executionTimeMillis" : 0,
"totalKeysExamined" : 0,
"totalDocsExamined" : 10,
"executionStages" : {
"stage" : "COLLSCAN",
...
},
...
},
...
}
queryPlanner.winningPlan.stage:COLLSCANキー値は、mongodがコレクションドキュメント全体をスキャンして結果を特定する必要があることを示します。そのため、コストのかかる操作になり、クエリが遅くなります。
executeStats.totalKeysExamined:0は、コレクションがインデックス作成戦略を利用していないことを意味します
特定のクエリでは、関連するドキュメントの数はゼロに近い必要があります。ドキュメントの数が非常に多い場合は、次の2つの可能性があります。
コレクションのインデックスを作成するには、次のコマンドを実行します。
db.collection.createIndex( { quantity: 1 } )
ここで、数量は、インデックス作成戦略に最適であると選択したフィールドの例です。
インデックス作成と使用するインデックス作成戦略について詳しく知りたい場合は、このブログを確認してください
データベースのパフォーマンスの低下は、クエリが遅いことで簡単に表現できます。これは、プラットフォームユーザーが遭遇する可能性が最も低いものです。プロファイラーを有効にして仕様に合わせて構成するか、実行中のmongodインスタンスでdb.currentOp()を実行することで、MongoDBで低速のクエリを特定できます。
返された結果の時間パラメータを確認することで、どのクエリが遅れているかを特定できます。これらのクエリを特定した後、これらのクエリでExplainヘルパーを使用して、たとえばクエリがインデックスを使用している場合など、詳細を取得します。
インデックスを作成しないと、変更を適用する前に多くのドキュメントをスキャンする必要があるため、操作にコストがかかります。この後退により、CPUが過負荷になり、クエリが遅くなり、CPUスパイクが上昇します。
クエリの速度低下につながる主な間違いは、実行計画が非効率的であり、関連するコレクションでインデックスを使用することで簡単に解決できます。