Azure Cosmos DB を使用したクエリ パフォーマンスのチューニング

適用対象: NoSQL

Azure Cosmos DB にはデータのクエリを実行するための NoSQL 用 API が用意されており、スキーマやセカンダリ インデックスは必要ありません。 この記事は、開発者を対象とした次の情報を提供します。

  • Azure Cosmos DB の SQL クエリの実行構造の詳細
  • クエリ パフォーマンスに関するヒントとベスト プラクティス
  • SQL クエリ実行メトリックを使用してクエリのパフォーマンスをデバッグする方法の例

SQL クエリの実行について

Azure Cosmos DB では、データはコンテナーに格納され、任意の ストレージ サイズに拡張したり、スループット を要求したりできます。 Azure Cosmos DB は、データの増加やプロビジョニングされたスループットの増加に対応するために、内部の物理パーティション間でデータをシームレスにスケーリングします。 REST API またはサポートされている SQL SDK のいずれかを使用して任意のコンテナーに SQL クエリを発行できます。

パーティション分割の概要: "city" などのパーティション キーを定義すると、複数の物理パーティションにわたるデータの分割方法が決定されます。 1 つのパーティション キー ("city" == "Seattle" など) に属するデータは物理パーティション内に格納され、1 つの物理パーティションに複数のパーティション キーのデータを格納できます。 パーティションがストレージの制限に達すると、サービスはシームレスにパーティションを 2 つの新しいパーティションに分割します。 データは新しいパーティションに均等に分散され、1 つのパーティション キーのすべてのデータがまとめて保持されます。 パーティションは一時的なものであるため、API はパーティション キー ハッシュの範囲を表すパーティション キーの範囲の抽象化を使用します。

Azure Cosmos DB にクエリを発行すると、SDK はこれらの論理手順を実行します。

  • SQL クエリを解析してクエリ実行プランを決定します。
  • クエリに SELECT * FROM c WHERE c.city = "Seattle" のようなパーティション キーに対するフィルターが含まれている場合は、1 つのパーティションに送られます。 クエリにパーティション キーに対するフィルターがない場合は、すべてのパーティションで実行され、各パーティションの結果がクライアント側でマージされます。
  • クエリは、クライアントの構成に基づいて、各パーティション内で順次または並行して実行されます。 各パーティション内では、クエリは、クエリの複雑性、構成されたページ サイズ、コレクションにプロビジョニングされたスループットに応じて 1 回以上のラウンド トリップを実行します。 各実行は、クエリの実行とクエリ実行の統計情報によって使用された 要求ユニット の数を返します。
  • SDK は複数のパーティションにわたるクエリ結果のサマリーを作成します。 たとえば、複数のパーティションにわたって ORDER BY が実行された場合、各パーティションからの結果がマージ、並べ替えられて、全体で並べ替えられた結果が返されます。 クエリが COUNT のような集計の場合、各パーティションからの count 数を合計して合計 count 数を算出します。

SDK は、クエリの実行にさまざまなオプションを提供します。 たとえば、.NET では、これらのオプションは QueryRequestOptions クラスで提供されています。 次の表は、これらのオプションと、オプションがクエリ実行時間に及ぼす影響を示しています。

オプション 説明
EnableScanInQuery 要求されたフィルター パスのインデックス作成が無効になっている場合のみ適用されます。 インデックス作成をオプトアウトし、フル スキャンを使用してクエリを実行する場合は、true に設定する必要があります。
MaxItemCount サーバーへのラウンド トリップあたり返される最大項目数。 -1 に設定すると、サーバーが返す項目の数を管理できます。
MaxBufferedItemCount 並列クエリの実行中にクライアント側でバッファーできる項目の最大数。 正のプロパティ値を指定すると、バッファー内の項目の数が設定値に制限されます。 これを 0 未満に設定すると、バッファーする項目の数が自動的に決定されます。
MaxConcurrency 並列クエリの実行中にクライアント側で実行される同時実行操作の数を取得または設定します。 プロパティに正の値を設定すると、同時実行操作の数が設定された値に制限されます。 これを 0 未満に設定すると、システムが実行する同時実行操作の数が自動的に決定されます。
PopulateIndexMetrics インデックス メトリック の収集を有効にして、クエリ エンジンが既存のインデックスをどのように使用し、潜在的な新しいインデックスをどのように使用できるかを理解できるようにします。 このオプションではオーバーヘッドが発生するため、低速クエリをデバッグする場合にのみ有効にする必要があります。
ResponseContinuationTokenLimitInKb サーバーによって返される継続トークンの最大サイズを制限することができます。 アプリケーション ホストで応答ヘッダー のサイズに制限があるが、クエリに使用される全体的な期間と RU が増加する可能性がある場合は、これを設定する必要があります。

たとえば、.NET SDK を使用して /city でパーティション分割されたコンテナーに対するクエリを次に示します:

QueryDefinition query = new QueryDefinition("SELECT * FROM c WHERE c.city = 'Seattle'");
QueryRequestOptions options = new QueryRequestOptions()
{
    MaxItemCount = -1,
    MaxBufferedItemCount = -1,
    MaxConcurrency = -1,
    PopulateIndexMetrics = true
};
FeedIterator<dynamic> feedIterator = container.GetItemQueryIterator<dynamic>(query);

FeedResponse<dynamic> feedResponse = await feedIterator.ReadNextAsync();

各クエリ実行は、クエリ要求オプションと本文内の SQL クエリに対して設定されたヘッダーを持つ REST API POST に対応します。 REST API の要求ヘッダーとオプションの詳細については、「REST API を使用しているリソースを照会する」を参照してください。

クエリ パフォーマンスに関するベスト プラクティス

一般に、Azure Cosmos DB クエリのパフォーマンスに最も大きな影響を与える要因は次のとおりです。 この記事では、これらの各要因について詳しく説明します。

要因 ヒント
プロビジョニング スループット クエリごとの RU を測定し、クエリに必要なプロビジョニング スループットがあることを確認します。
パーティション分割とパーティション キー 待ち時間を短くするために、フィルター句と一致するパーティション キー値のクエリが優先されます。
SDK とクエリのオプション 直接接続、クライアント側のクエリ実行オプションの調整など、SDK のベスト プラクティスに従ってください。
ネットワーク待機時間 待機時間を短縮するために、可能な限り Azure Cosmos DB アカウントと同じリージョンでアプリケーションを実行します。
インデックス作成ポリシー クエリに必要なインデックス作成パス/ポリシーがあることを確認してください。
クエリ実行メトリック クエリ実行メトリックを分析して、クエリおよびデータ図形の書き換えの必要性を特定します。

プロビジョニング スループット

Azure Cosmos DB では、秒あたりの要求ユニット (RU) で表される予約済みスループットで各データのコンテナーを作成します。 1 KB のドキュメントの読み取りは 1 RU であり、すべての操作 (クエリを含む) は、その複雑さに基づいて固定数の RU に正規化されます。 たとえば、コンテナーに対して 1000 RU/秒がプロビジョニングされていて、5 RU を消費する SELECT * FROM c WHERE c.city = 'Seattle' のようなクエリがある場合は、1 秒あたり (1000 RU/秒) / (5 RU/クエリ) = 200 のクエリを実行できます。

1 秒に 200 を超えるクエリ (またはプロビジョニングされたすべての RU を飽和するその他の操作) を送信すると、サービスは受信要求のレート制限を開始します。 SDK はバックオフ/再試行を実行することでレート制限を自動的に処理するため、これらのクエリの待機時間が長くなる場合があります。 プロビジョニング スループットを必要な値に引き上げることで、クエリの待ち時間やスループットが改善されます。

要求ユニットの詳細については、 「要求ユニット」を参照してください。

パーティション分割とパーティション キー

Azure Cosmos DB では、データを読み取るための次のシナリオは、通常、最速/最も効率的なものから最も低速/最も非効率的なものまで順に並べ替えます。

  • 単一のパーティション キーと項目 ID (ポイント読み取りとも呼ばれます) に対する GET
  • 1 つのパーティション キーに 1 つのフィルター句を含むクエリ
  • 任意のプロパティに対して等値または範囲フィルター句を使用するクエリ
  • フィルターを使用しないクエリ

すべてのパーティションで実行する必要があるクエリでは、待機時間が長く、消費する RU が高くなる可能性があります。 各パーティションは、すべてのプロパティに対して自動インデックス作成機能があるため、この場合、インデックスからクエリを効率的に実行できます。 並行処理オプションを使用すると、複数のパーティションにまたがってクエリを実行できます。

パーティション分割とパーティション キーの詳細については、「Azure Cosmos DB のパーティション分割」をご覧ください。

SDK とクエリのオプション

SDK を使用して Azure Cosmos DB でクライアント側のパフォーマンスを最適化する方法については、「クエリ パフォーマンスに関するヒント」と「パフォーマンス テスト」を参照してください。

ネットワーク待機時間

グローバル配布を設定し、最も近い地域に接続する方法については、「Azure Cosmos DB グローバル配布」を参照してください。 ネットワーク待ち時間は、複数のラウンド トリップを実行したり、クエリから大量の結果セットを取得する必要がある場合は、クエリのパフォーマンスに大きな影響を及ぼします。

クエリ実行メトリック を使用して、クエリのサーバー実行時間を取得でき、クエリ実行に費やされた時間とネットワーク転送に費やされた時間を区別できます。

インデックス作成ポリシー

インデックス作成のパス、種類、およびモードと、クエリ実行への影響については、「インデックス作成ポリシーを構成する」を参照してください。 既定では、Azure Cosmos DB はすべてのデータに自動インデックス作成を適用し、文字列と数値に範囲インデックスを使用します。これは等価クエリに有効です。 ハイ パフォーマンスの挿入シナリオでは、各挿入操作の RU コストを削減するためにパスを除外することを検討してください。

インデックス メトリック を使用して、各クエリに使用されるインデックスと、クエリのパフォーマンスを向上させるために不足しているインデックスを識別できます。

クエリ実行メトリック

要求の 診断 のクエリ実行ごとに詳細なメトリックが返されます。 これらのメトリックは、クエリの実行中に時間が費やされる場所を示し、高度なトラブルシューティングを有効にします。

クエリ メトリックの取得 の詳細について説明します。

メトリック ユニット 説明
TotalTime ミリ秒 クエリ実行時間の合計
DocumentLoadTime ミリ秒 ドキュメントの読み込みに費やされた時間
DocumentWriteTime ミリ秒 出力ドキュメントの書き込みとシリアル化に費やされた時間
IndexLookupTime ミリ秒 物理インデックス レイヤーでかかった時間
QueryPreparationTime ミリ秒 クエリの準備に費やされた時間
RuntimeExecutionTime ミリ秒 クエリ ランタイム実行時間の合計
VMExecutionTime ミリ秒 クエリの実行に費やされたクエリ ランタイムの時間
OutputDocumentCount count 結果セット内の出力ドキュメントの数
OutputDocumentSize count 出力されたドキュメントの合計サイズ (バイト単位)
RetrievedDocumentCount count 取得されたドキュメントの合計数
RetrievedDocumentSize バイト バイト単位で取得されたドキュメントの合計サイズ (バイト)
IndexHitRatio 比率 [0,1] 読み込まれたドキュメント数に対するフィルターに一致したドキュメントの数の比率

クライアント SDK は、内部的に複数のクエリ要求を作成して、各パーティション内でクエリを処理できます。 合計結果が最大項目数要求オプションを超えた場合、クエリがパーティションのプロビジョニングスループットを超えた場合、クエリペイロードがページあたりの最大サイズに達した場合、またはクエリがシステム割り当てタイムアウト制限に達した場合、クライアントはパーティションごとに複数の呼び出しを行います。 部分的なクエリの実行ごとに、そのページのクエリ メトリックが返されます。

以下は、クエリの例とクエリ実行から返されたメトリックを解釈する方法です。

クエリ メトリックの例 説明
SELECT TOP 100 * FROM c "RetrievedDocumentCount": 101 取得されたドキュメントの数は、TOP 句と一致した 100 + 1 になっています。 クエリ時間はスキャンであるため、大部分が WriteOutputTimeDocumentLoadTime で費やされています。
SELECT TOP 500 * FROM c "RetrievedDocumentCount": 501 RetrievedDocumentCount が高くなっています (TOP 句と一致する 500 + 1 になっています)。
SELECT * FROM c WHERE c.N = 55 "IndexLookupTime": "00:00:00.0009500" /N/? に対するインデックス参照であるため、IndexLookupTime のキー参照に約 0.9 ミリ秒かかっています。
SELECT * FROM c WHERE c.N > 55 "IndexLookupTime": "00:00:00.0017700" /N/? に対するインデックス参照であるため、範囲スキャンの IndexLookupTime では、これより少し長い 1.7 ミリ秒かかっています。
SELECT TOP 500 c.N FROM c "IndexLookupTime": "00:00:00.0017700" DocumentLoadTime では前のクエリと同じ時間がかかっていますが、プロジェクションさているプロパティが 1 つのみのため、より短い DocumentWriteTime になっています。
SELECT TOP 500 udf.toPercent(c.N) FROM c "RuntimeExecutionTime": "00:00:00.2136500" 各値が c.N の UDF を実行するために、RuntimeExecutionTime で約 213 ミリ秒かかっています。
SELECT TOP 500 c.Name FROM c WHERE STARTSWITH(c.Name, 'Den') "IndexLookupTime": "00:00:00.0006400", "RuntimeExecutionTime": "00:00:00.0074100" IndexLookupTime/Name/? では約 0.6 ミリ秒かかっています。 RuntimeExecutionTime でのほとんどのクエリの実行時間 は最大 7 ミリ秒です。
SELECT TOP 500 c.Name FROM c WHERE STARTSWITH(LOWER(c.Name), 'den') "IndexLookupTime": "00:00:00", "RetrievedDocumentCount": 2491, "OutputDocumentCount": 500 LOWER を使用しているため、クエリはスキャンとして実行され、2491 の取得ドキュメント中、500 ドキュメントが返されました。

次のステップ

  • サポートされている SQL クエリ演算子とキーワードの詳細については、「SQL クエリ」を参照してください。
  • 要求ユニットの詳細については、「要求ユニット」を参照してください。
  • インデックス作成ポリシーの詳細については、「インデックス作成ポリシー」を参照してください