パフォーマンスとリソース使用率 - Amazon DocumentDB

パフォーマンスとリソース使用率

このセクションでは、Amazon DocumentDB デプロイにおける一般的な診断の問題に関する質問と解決策を示します。提供されている例では mongo シェルを使用し、個々のインスタンスに範囲を設定しています。インスタンスエンドポイントを見つけるには、「Amazon DocumentDB エンドポイントについて」を参照してください 。

長時間実行されているクエリやブロックされているクエリを見つけて終了する方法

ユーザークエリの実行は、最適でないクエリプランのために遅くなることも、リソースの競合のためにブロックされる可能性もあります。

クエリプランが最適でないために長時間実行されているクエリ、またはリソースの競合のためにブロックされているクエリを見つけるには、currentOp コマンドを使用します。コマンドをフィルタリングすると、終了する関連クエリのリストを絞り込むことができます。クエリを終了できるようにするには、長時間実行されているクエリに opid を関連付ける必要があります。

次のクエリでは、currentOp コマンドを使用して、ブロックされている、または 10 秒以上実行されているクエリをすべてリストします。

db.adminCommand({ aggregate: 1, pipeline: [ {$currentOp: {}}, {$match: {$or: [ {secs_running: {$gt: 10}}, {WaitState: {$exists: true}}]}}, {$project: {_id:0, opid: 1, secs_running: 1}}], cursor: {} });

次に、クエリを絞り込み、10 秒以上実行されているクエリの opid を見つけて終了できます。

10 秒以上実行されているクエリを見つけ出して終了するには

  1. クエリの opid を見つけます。

    db.adminCommand({ aggregate: 1, pipeline: [ {$currentOp: {}}, {$match: {$or: [{secs_running: {$gt: 10}}, {WaitState: {$exists: true}}]}}], cursor: {} });

    このオペレーションによる出力は、次のようになります (JSON 形式)。

    { "waitedMS" : NumberLong(0), "cursor" : { "firstBatch" : [ { "opid" : 24646, "secs_running" : 12 } ], "id" : NumberLong(0), "ns" : "admin.$cmd" }, "ok" : 1 }
  2. killOp オペレーションを使用してクエリを終了します。

    db.adminCommand({killOp: 1, op: 24646});

クエリプランを参照してクエリを最適化する方法

クエリの実行が遅い場合は、関連するドキュメントを選択するためにクエリの実行でコレクションのフルスキャンが必要である可能性があります。場合によっては、適切なインデックスの作成により、クエリの実行を高速化できます。このシナリオを検出し、インデックスを作成するフィールドを決定するには、explain コマンドを使用します。

注記

Amazon DocumentDB は、耐障害性が高い、分散型の自己復旧ストレージシステムを利用する専用データベースエンジンで MongoDB 3.6 API をエミュレートします。その結果、クエリプランと explain() の出力は、Amazon DocumentDB と MongoDB の間で異なる場合があります。クエリプランを制御する場合は、$hint 演算子を使用して優先インデックスの選択を強制できます。

次のように、explain コマンドで改善する必要があるクエリを実行します。

db.runCommand({explain: {<query document>}})

以下に示しているのは、オペレーションの例です。

db.runCommand({explain:{ aggregate: "sample-document", pipeline: [{$match: {x: {$eq: 1}}}], cursor: {batchSize: 1}} });

このオペレーションによる出力は、次のようになります (JSON 形式)。

{ "queryPlanner" : { "plannerVersion" : 1, "namespace" : "db.test", "winningPlan" : { "stage" : "COLLSCAN" } }, "serverInfo" : { "host" : "...", "port" : ..., "version" : "..." }, "ok" : 1 }

前述の出力は、$match ステージでコレクション全体をスキャンし、各ドキュメントのフィールド "x" が 1 に等しいかどうか確認する必要があることを示しています。コレクションに多くのドキュメントがある場合、コレクションのスキャン(および全体的なクエリのパフォーマンス)は非常に低速になります。したがって、explain コマンドの出力に "COLLSCAN" が存在することは、適切なインデックスを作成することで、クエリのパフォーマンスを向上できることを示します。

この例では、クエリはすべてのドキュメントでフィールド "x" が 1 と等しいかどうかを確認します。したがって、フィールド "x" でインデックスを作成すると、クエリは完全なコレクションスキャンを回避し、インデックスを使用して関連ドキュメントをより早く返すことができます。

フィールド "x" でインデックスを作成した後、explain 出力は次のようになります。

{ "queryPlanner" : { "plannerVersion" : 1, "namespace" : "db.test", "winningPlan" : { "stage" : "IXSCAN", "indexName" : "x_1", "direction" : "forward" } }, "serverInfo" : { "host" : "...", "port" : ..., "version" : "..." }, "ok" : 1 }

フィールド "x" にインデックスを作成すると、$match ステージはインデックススキャンを使用して、述語 "x = 1" を評価する必要があるドキュメントの数を減らすことができます。

小規模なコレクションで、パフォーマンスの向上がごくわずかである場合、Amazon DocumentDB クエリプロセッサはインデックスを使用しないように選択することがあります。

インスタンスで実行中のすべての操作をリストする方法

ユーザーまたはマスターユーザーは、診断およびトラブルシューティングの目的で、インスタンスで実行されている現在のすべての操作を頻繁にリストします。(ユーザーの管理の詳細については、Amazon DocumentDB ユーザーの管理 を参照してください。)

mongo シェルで、次のクエリを使用して、Amazon DocumentDB インスタンスで実行中のすべてのオペレーションをリストできます。

db.adminCommand({currentOp: 1, $all: 1});

クエリは、インスタンス上で現在動作しているすべてのユーザークエリと内部システムタスクの完全なリストを返します。

このオペレーションによる出力は、次のようになります (JSON 形式)。

{ "inprog" : [ { "desc" : "INTERNAL" }, { "desc" : "TTLMonitor", "active" : false }, { "desc" : "GARBAGE_COLLECTION" }, { "client" : ..., "desc" : "Conn", "active" : true, "killPending" : false, "opid" : 195, "ns" : "admin.$cmd", "command" : { "currentOp" : 1, "$all" : 1 }, "op" : "command", "$db" : "admin", "secs_running" : 0, "microsecs_running" : NumberLong(68), "clientMetaData" : { "application" : { "name" : "MongoDB Shell" }, "driver" : { ... }, "os" : { ... } } }], "ok" : 1 }

次に示すのは、"desc" フィールドの有効な値です。

  • INTERNAL — カーソルのクリーンアップや古いユーザーのクリーンアップタスクなどの内部システムタスク。

  • TTLMonitor — 有効期限 (TTL) モニタリングスレッド。その実行ステータスが "active" フィールドに反映されています。

  • GARBAGE_COLLECTION — 内部ガベージコレクタースレッド。最大 3 つのガベージコレクタースレッドをシステムで同時に実行できます。

  • CONN — ユーザーのクエリ。

前の出力では、システムで実行されているすべてのユーザークエリもリストされます。各ユーザークエリはデータベースとコレクションのコンテキストで実行され、これらの 2 つを統合したものが、名前空間と呼ばれます。各ユーザークエリの名前空間は、"ns" フィールドで利用できます。

特定の名前空間で実行されているすべてのユーザークエリをリストする必要がある場合があります。したがって、前の出力は "ns" フィールドでフィルタリングする必要があります。次にフィルター処理の出力を達成するためのクエリの例を示します。クエリは、データベース "db" とコレクション "test"(つまり、"db.test" 名前空間)で現在実行されているすべてのユーザークエリをリストします。

db.adminCommand({aggregate: 1, pipeline: [{$currentOp: {allUsers: true, idleConnections: true}}, {$match: {ns: {$eq: "db.test"}}}], cursor: {} });

システムのマスターユーザーは、すべてのユーザーのクエリと、すべての内部システムタスクを表示できます。他のすべてのユーザーは、各自のクエリのみを表示できます。

クエリと内部システムタスクの合計数が、デフォルトのバッチカーソルサイズを超えている場合、mongo シェルは残りの結果を表示するイテレーターオブジェクト 'it' を自動的に生成します。すべての結果がなくなるまで、コマンド 'it' の実行を継続します。

クエリが進行するタイミングを知る方法

ユーザーのクエリは、最適とはいえないクエリプランが原因で実行速度が遅くなったり、リソースの競合が原因でブロックされたりすることがあります。このようなクエリのデバッグは複数ステップのプロセスであり、同じステップを複数回実行することが必要になる場合があります。

デバッグの最初のステップでは、長時間実行されているか、ブロックされているすべてのクエリをリストします。次のクエリは、10 秒以上実行されているか、リソースを待機しているすべてのユーザークエリをリストします。

db.adminCommand({aggregate: 1, pipeline: [{$currentOp: {}}, {$match: {$or: [{secs_running: {$gt: 10}}, {WaitState: {$exists: true}}]}}, {$project: {_id:0, opid: 1, secs_running: 1, WaitState: 1, blockedOn: 1, command: 1}}], cursor: {} });

前述のクエリを定期的に繰り返して、クエリのリストが変更されているかどうかを確認し、実行時間が長いクエリまたはブロックされているクエリを識別します。

対象のクエリの出力ドキュメントに WaitState フィールドがある場合、クエリの実行が低速であるかブロックされている理由は、リソースの競合であることを示しています。リソースの競合は、I/O、内部システムタスク、またはその他のユーザークエリが原因である可能性があります。

このオペレーションによる出力は、次のようになります (JSON 形式)。

{ "waitedMS" : NumberLong(0), "cursor" : { "firstBatch" : [ { "opid" : 201, "command" : { "aggregate" : ... }, "secs_running" : 208, "WaitState" : "IO" } ], "id" : NumberLong(0), "ns" : "admin.$cmd" }, "ok" : 1 }

同じインスタンスで同時に実行されているさまざまなコレクションで多くのクエリが実行されているか、クエリを実行中のデータセットのインスタンスサイズが小さすぎると、I/O がボトルネックとなる場合があります。クエリが読み取り専用クエリである場合、各コレクションのクエリを別々のレプリカに分離することで、以前の状況を軽減できます。さまざまなコレクション間で同時更新を行う場合、またはデータセットに対するインスタンスサイズが小さすぎる場合の軽減策は、インスタンスをスケールアップすることです。

リソースの競合の原因が他のユーザークエリである場合、出力ドキュメントの "blockedOn" フィールドに、このクエリに影響しているクエリの "opid" が含まれます。"opid" を使用して、すべてのクエリの "WaitState" および "blockedOn" フィールドのチェーンに従って、チェーンの先頭にあるクエリを調べます。

チェーンの先頭にあるタスクが内部タスクである場合の唯一の緩和策は、クエリを終了し、後で再実行することです。

以下は、検索クエリが別のタスクによって所有されているコレクションロックでブロックされるサンプル出力です。

{ "inprog" : [ { "client" : "...", "desc" : "Conn", "active" : true, "killPending" : false, "opid" : 75, "ns" : "...", "command" : { "find" : "...", "filter" : { } }, "op" : "query", "$db" : "test", "secs_running" : 9, "microsecs_running" : NumberLong(9449440), "threadId" : 24773, "clientMetaData" : { "application" : { "name" : "MongoDB Shell" }, "driver" : { ... }, "os" : { ... } }, "WaitState" : "CollectionLock", "blockedOn" : "INTERNAL" }, { "desc" : "INTERNAL" }, { "client" : "...", ... "command" : { "currentOp" : 1 }, ... } ], "ok" : 1 }

"WaitState" の値が "Latch""SystemLock""BufferLock""BackgroundActivity"、または "Other" の場合、リソース競合の原因は内部システムタスクにあります。この状態が長時間続く場合、唯一の緩和策は、クエリを終了し、後で再実行することです。

システムの実行速度が突然遅くなった理由を判断する方法

以下にシステムの速度が低下する一般的な理由を示します。

  • 同時クエリ間の過剰なリソースの競合

  • 時間の経過とともに増加するアクティブな同時クエリの数

  • "GARBAGE_COLLECTION" などの内部システムタスク

時間の経過とともにシステムの使用状況をモニタリングするには、以下の "currentOp" クエリを定期的に実行し、外部ストアに結果を出力します。クエリは、システムの各名前空間内のクエリとオペレーションの数をカウントします。次に、システムの使用状況の結果を分析し、システムへの負荷を把握して適切なアクションを行うことができます。

db.adminCommand({aggregate: 1, pipeline: [{$currentOp: {allUsers: true, idleConnections: true}}, {$group: {_id: {desc: "$desc", ns: "$ns", WaitState: "$WaitState"}, count: {$sum: 1}}}], cursor: {} });

このクエリは、各名前空間で実行されているすべてのクエリとすべての内部システムタスクの合計を返します。また、名前空間ごとに存在する場合は、待機状態の一意の数を返します。

このオペレーションによる出力は、次のようになります (JSON 形式)。

{ "waitedMS" : NumberLong(0), "cursor" : { "firstBatch" : [ { "_id" : { "desc" : "Conn", "ns" : "db.test", "WaitState" : "CollectionLock" }, "count" : 2 }, { "_id" : { "desc" : "Conn", "ns" : "admin.$cmd" }, "count" : 1 }, { "_id" : { "desc" : "TTLMonitor" }, "count" : 1 } ], "id" : NumberLong(0), "ns" : "admin.$cmd" }, "ok" : 1 }

上記の出力では、コレクションロックでブロックされている名前空間 "db.test" に 2 つのユーザークエリ、名前空間 "admin.$cmd" に 1 つのクエリ、および 1 つの内部 "TTLMonitor" タスクがあります。

出力が、ブロック待機状態の多くのクエリを示す場合は、「長時間実行されているクエリやブロックされているクエリを見つけて終了する方法」を参照してください。

1 つ以上のクラスターインスタンスで CPU 使用率が高くなる原因を特定する方法

以下のセクションは、インスタンスの CPU 使用率が高い原因を特定するのに役立ちます。結果は、ワークロードによって異なります。

インスタンスの CPU 使用率が高い理由に応じて、以下の 1 つ以上の操作を行うと役立ちます。

  • プライマリインスタンスで高い CPU 使用率が示され、レプリカインスタンスではそうではない場合、クライアントの読み込み優先設定(secondaryPreferred など)を使用してレプリカ間で読み込みトラフィックを分散することを検討してください。詳細については、「レプリカセットとして Amazon DocumentDB に接続する」を参照してください。

    レプリカを読み込みに使用すると、プライマリインスタンスでより多くの書き込みトラフィックを処理できるようになり、クラスターのリソースをより有効に活用できます。レプリカからの読み込みには結果整合性があります。

  • CPU 使用率が高いことが書き込みワークロードの結果である場合、クラスターのインスタンスのサイズをより大きいインスタンスタイプに変更すると、ワークロードに対応する CPU コアの数が増えます。詳細については、「インスタンス」および「インスタンスクラスの仕様」を参照してください。

  • すべてのクラスターインスタンスで高い CPU 使用率が示され、ワークロードでレプリカが読み取りに使用されている場合、クラスターにレプリカを追加すると、読み取りトラフィックに使用できるリソースが増えます。詳細については、「Amazon DocumentDB インスタンスへのクラスターの追加」を参照してください。

インスタンスで開いているカーソルを確認する方法

Amazon DocumentDB インスタンスに接続している場合、db.runCommand("listCursors") コマンドを使用して、そのインスタンスで開いているカーソルを一覧表示できます。特定の Amazon DocumentDB インスタンスで特定の時間に開くことができるアクティブカーソルの数は 450 個に制限されています。通常、カーソルはインスタンスのリソースを消費し、開いている数に上限があるため、不要になったカーソルは閉じることをお勧めします。

db.runCommand("listCursors")

現在の Amazon DocumentDB エンジンのバージョンを確認する方法

現在の Amazon DocumentDB エンジンのバージョンを確認するには、次のコマンドを実行します。

db.runCommand({getEngineVersion: 1})

このオペレーションによる出力は、次のようになります (JSON 形式)。

{ "engineVersion" : "1.0.202313", "ok" : 1 }

未使用のインデックスを識別する方法

パフォーマンスを向上させ、コストを削減するために、ベストプラクティスとして、未使用のインデックスを定期的に識別して削除することをお勧めします。インデックスを維持するために不必要なコンピューティング、ストレージ、I/O が使用されなくなるためです。特定のコレクションのインデックスを識別するには、次のコマンドを実行します。

db.collection.getIndexes()

インデックスが使用されているかどうかを識別するには、次のコマンドを実行します。コマンドの出力では、次のことがわかります。

db.collection.aggregate([{$indexStats:{}}]).pretty()
  • ops — インデックスを使用したオペレーションの数。ワークロードが十分に長時間実行されており、ワークロードが安定していることがはっきりわかる場合、ops 値 0 は、インデックスがまったく使用されていないことを示しています。

  • since — インデックス使用状況に関する統計情報の収集が Amazon DocumentDB により開始されてからの時間。通常は、前回のデータベース再起動またはメンテナンスアクション以降の値です。

コレクションの全体的なインデックスサイズを確認するには、次のコマンドを実行します。

db.collection.stats()

未使用のインデックスを削除するには、次のコマンドを実行します。

db.collection.dropIndex("indexName")

欠落しているインデックスを識別する方法

Amazon DocumentDB プロファイラを使用して、低速なクエリをログに記録できます。低速なクエリのログに繰り返し現れるクエリがある場合、そのクエリのパフォーマンスを向上させるために追加のインデックスが必要であることを示している可能性があります。

少なくとも 1 つの COLLSCAN ステージで実行される 1 つ以上のステージを持つ長時間実行クエリ (つまり、クエリのステージは、クエリへのレスポンスを生成するために、コレクション内のすべてのドキュメントを読み取る必要がある) を探すと、どのインデックスが役に立つ可能性があるかを見分けることができます。

次の例は、大規模なコレクションで実行されたタクシー乗車のコレクションに対するクエリを示しています。

db.rides.count({"fare.totalAmount":{$gt:10.0}}))

この例を実行するには、fare.totalAmount フィールドにインデックスがないため、クエリはコレクションスキャン (つまり、コレクション内のすべてのドキュメントを読み込む) を実行する必要がありました。このクエリの Amazon DocumentDB プロファイラからの出力は、次のようになります。

{ ... "cursorExhausted": true, "nreturned": 0, "responseLength": 0, "protocol": "op_query", "millis": 300679, "planSummary": "COLLSCAN", "execStats": { "stage": "COLLSCAN", "nReturned": "0", "executionTimeMillisEstimate": "300678.042" }, "client": "172.31.5.63:53878", "appName": "MongoDB Shell", "user": "example" }

この例のクエリを高速化するには、以下に示すように fare.totalAmount にインデックスを作成します。

db.rides.createIndex( {"fare.totalAmount": 1}, {background: true} )
注記

フォアグラウンドで作成されたインデックス (つまり、インデックスの作成時に {background:true} オプションが指定されなかった場合) は、排他的な書き込みロックを使用します。これにより、インデックス構築が完了するまでアプリケーションがコレクションにデータを書き込めなくなります。本番稼働クラスターでインデックスを作成する場合は、このような影響が生じる可能性がある点に注意してください。インデックスを作成するときは、{background:true} を設定することをお勧めします。

一般に、カーディナリティが高いフィールド (たとえば、多数の一意の値) でインデックスを作成します。カーディナリティの低いフィールドにインデックスを作成すると、使用されないインデックスが大きくなることがあります。Amazon DocumentDB クエリオプティマイザは、クエリプランの作成時に、コレクション全体のサイズとインデックスの選択性を考慮します。インデックスが存在する場合でも、クエリプロセッサが COLLSCAN を選択することがあります。これは、インデックスを利用しても、コレクション全体をスキャンした場合よりパフォーマンスが高くならないとクエリプロセッサが推定したときに発生します。特定のインデックスの利用をクエリプロセッサに強制する場合、以下に示すように、hint() 演算子を使用できます。

db.collection.find().hint("indexName")

便利なクエリの概要

以下のクエリは、Amazon DocumentDB のパフォーマンスとリソース使用率をモニタリングするのに役立ちます 。

  • 次のクエリを使用して、すべてのアクティビティをリストします。

    db.adminCommand({currentOp: 1, $all: 1});
  • 次のコードは、実行時間が長いクエリまたはブロックされたすべてのクエリをリストします。

    db.adminCommand({aggregate: 1, pipeline: [{$currentOp: {}}, {$match: {$or: [{secs_running: {$gt: 10}}, {WaitState: {$exists: true}}]}}, {$project: {_id:0, opid: 1, secs_running: 1, WaitState: 1, blockedOn: 1, command: 1}}], cursor: {} });
  • 次のコードは、クエリを終了します。

    db.adminCommand({killOp: 1, op: <opid of running or blocked query>});
  • 次のコードを使用して、システム状態の集約ビューを取得します。

    db.adminCommand({aggregate: 1, pipeline: [{$currentOp: {allUsers: true, idleConnections: true}}, {$group: {_id: {desc: "$desc", ns: "$ns", WaitState: "$WaitState"}, count: {$sum: 1}}}], cursor: {} });