次の方法で共有


Azure Cosmos DB SDK のクエリのパフォーマンスに関するヒント

適用対象: NoSQL

Azure Cosmos DB は、高速で柔軟性に優れた分散データベースです。待ち時間とスループット レベルが保証されており、シームレスにスケーリングできます。 Azure Cosmos DB でデータベースをスケーリングするために、アーキテクチャを大きく変更したり、複雑なコードを記述したりする必要はありません。 スケールアップとスケールダウンは、API 呼び出しを 1 回行うだけの簡単なものです。 詳細については、コンテナーのスループットのプロビジョニングまたはデータベースのスループットのプロビジョニングに関するページを参照してください。

クエリ プランの呼び出しを減らす

クエリを実行するには、クエリ プランを作成する必要があります。 Azure Cosmos DB ゲートウェイに対するネットワーク要求は、クエリ操作の待機時間に追加されます。 この要求をなくし、クエリ操作の待機時間を短縮するには、2 つの方法があります。

オプティミスティック直接実行を使用して単一パーティション クエリを最適化する

Azure Cosmos DB NoSQL には、オプティミスティック直接実行 (ODE) と呼ばれる最適化があり、特定の NoSQL クエリの効率を向上させることができます。 具体的には、分散を必要としないクエリには、単一の物理パーティションで実行できるクエリや、改ページを必要としない応答があるクエリが含まれます。 分散を必要としないクエリでは、クライアント側のクエリ プランの生成やクエリの書き換えなど、一部のプロセスを確実にスキップできるため、クエリの待機時間と要求ユニット (RU) のコストが削減されます。 要求またはクエリ自体の中でパーティション キーを指定する (または物理パーティションが 1 つしかない) 場合、かつクエリの結果に改ページ処理が必要ない場合に、ODE はクエリを改善できます。

注意

分散を必要としないクエリのパフォーマンスが向上する ODE は、アプリケーションをバックエンド レプリカに接続するためのパスである ダイレクト モードと混同しないでください。

ODE は、.NET SDK バージョン 3.38.0 以降で使用できるようになりました。 クエリを実行し、要求またはクエリ自体でパーティション キーを指定する場合、またはデータベースに物理パーティションが 1 つだけある場合、クエリ実行では ODE の利点を使用できます。 ODE を有効にするには、QueryRequestOptions で EnableOptimisticDirectExecution を true に設定します。

GROUP BYORDER BYDISTINCT、集計関数 (合計、平均、最小、最大など) を特徴とする単一パーティション クエリは、ODE を使用することで大きなメリットを得ることができます。 ただし、クエリが複数のパーティションを対象とする場合や、改ページ処理が必要なシナリオでは、クエリ応答の待機時間と RU コストが ODE を使用しない場合よりも長くなる可能性があります。 したがって、ODE を使用する場合は、次の手順を実行する必要があります。

  • 呼び出しまたはクエリ自体の中でパーティション キーを指定します。
  • データ サイズが増加してパーティションが分割される原因となっていないことを確認します。
  • ODE から最大限の恩恵を受けるために、クエリ結果が改ページ処理を必要としないことを確認します。

ODE のメリットを得られる単純な単一パーティション クエリの例をいくつか次に示します。

- SELECT * FROM r
- SELECT * FROM r WHERE r.pk == "value"
- SELECT * FROM r WHERE r.id > 5
- SELECT r.id FROM r JOIN id IN r.id
- SELECT TOP 5 r.id FROM r ORDER BY r.id
- SELECT * FROM r WHERE r.id > 5 OFFSET 5 LIMIT 3 

データ項目の数が時間の経過と同時に増加し、Azure Cosmos DB データベースによって パーティションが分割される場合、単一パーティション クエリでディストリビューションが必要になる場合があります。 これが発生する可能性があるクエリの例には次が含まれます。

- SELECT Count(r.id) AS count_a FROM r
- SELECT DISTINCT r.id FROM r
- SELECT Max(r.a) as min_a FROM r
- SELECT Avg(r.a) as min_a FROM r
- SELECT Sum(r.a) as sum_a FROM r WHERE r.a > 0 

一部の複雑なクエリは、単一パーティションをターゲットにしている場合でも、常に分散を必要とする可能性があります。 そのようなクエリの例には次が含まれます。

- SELECT Sum(id) as sum_id FROM r JOIN id IN r.id
- SELECT DISTINCT r.id FROM r GROUP BY r.id
- SELECT DISTINCT r.id, Sum(r.id) as sum_a FROM r GROUP BY r.id
- SELECT Count(1) FROM (SELECT DISTINCT r.id FROM root r)
- SELECT Avg(1) AS avg FROM root r 

重要なのは、ODE がクエリ プランを常に取得するとは限らないため、サポートされていないクエリを許可または無効にできないことに注意してください。 たとえば、パーティション分割後、このようなクエリは ODE の対象ではなくなったため、クライアント側のクエリ プランの評価によってブロックされるため、実行されません。 互換性/サービス継続性を確保するには、ODE を使用しないシナリオで完全にサポートされている (つまり、一般的なマルチパーティション ケースで実行されて正しい結果を生成する) クエリのみが ODE で使用されるようにすることが重要です。

注意

ODE を使用すると、新しい種類の継続トークンが生成される可能性があります。 このようなトークンは、古い SDK では設計上認識されず、結果として、間違った形式の継続トークン例外が発生する可能性があります。 新しい SDK から生成されたトークンが古い SDK によって使用されるシナリオがある場合は、次の 2 段階の方法でアップグレードすることをお勧めします。

  • 新しい SDK へのアップグレードと、ODE の無効化を 1 つのデプロイの一部として実施します。 すべてのノードがアップグレードされるのを待ちます。
  • ODE を無効にするには、QueryRequestOptions で EnableOptimisticDirectExecution を false に設定します。
  • 2 番目のデプロイの一部として、すべてのノードの ODE を有効にします。

ローカル クエリ プランの生成を使用する

SQL SDK には、クエリをローカルで解析および最適化するためのネイティブ ServiceInterop.dll が含まれています。 ServiceInterop.dll、Windows x64 プラットフォームでのみサポートされます。 次の種類のアプリケーションでは、既定で 32 ビットのホスト処理が使用されます。 ホスト処理を 64 ビット処理に変更するには、アプリケーションの種類に基づいて次の手順のようにします。

  • 実行可能なアプリケーションの場合は、[プロジェクトのプロパティ] ウィンドウの [ビルド] タブで [プラットフォーム ターゲット][x64] に設定することで、ホスト処理を変更できます。

  • VSTest ベースのテスト プロジェクトの場合は、Visual Studio の [テスト] メニューで >>[既定のプロセッサ アーキテクチャ] > [X64] の順に選択することで、ホスト処理を変更できます。

  • ローカルでデプロイされた ASP.NET Web アプリケーションの場合は、 [ツール]>>> の順に選択して、 [Web サイトおよびプロジェクト用 IIS Express の 64 ビット バージョンを使用する] をオンにすることで、ホスト処理を変更できます。

  • Azure にデプロイされた ASP.NET Web アプリケーションの場合は、Azure portal の [アプリケーションの設定]64 ビット プラットフォームを選択することで、ホスト処理を変更できます。

注意

既定では、新しい Visual Studio プロジェクトは、 [任意の CPU] に設定されます。 x86 に切り替わらないように、プロジェクトを x64 に設定することをお勧めします。 [任意の CPU] に設定されたプロジェクトは、x86 のみの依存関係が追加されると、簡単に x86 に切り替わる可能性があります。

ServiceInterop.dll は、SDK DLL が実行されるフォルダーに存在する必要があります。 これは、手動で DLL をコピーする場合、またはカスタム ビルドおよびデプロイ システムを使用する場合にのみ、問題になります。

単一パーティション クエリを使用する

QueryRequestOptions プロパティを設定してパーティション キーを対象とし、集計 (DistinctDCountGroup By など) を含まないクエリの場合。 この例では、/state のパーティション キー フィールドが値 Washington にフィルター処理されます。

using (FeedIterator<MyItem> feedIterator = container.GetItemQueryIterator<MyItem>(
    "SELECT * FROM c WHERE c.city = 'Seattle' AND c.state = 'Washington'"
{
    // ...
}

必要に応じて、要求オプション オブジェクトの一部としてパーティション キーを指定できます。

using (FeedIterator<MyItem> feedIterator = container.GetItemQueryIterator<MyItem>(
    "SELECT * FROM c WHERE c.city = 'Seattle'",
    requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey("Washington")}))
{
    // ...
}

重要

Linux や macOS などの Windows 以外の OS を実行しているクライアントでは、パーティション キーは 常に 要求オプション オブジェクトで指定する必要があります。

注意

クロスパーティション クエリでは、SDK が既存のすべてのパーティションにアクセスして結果を調べる必要があります。 コンテナーの物理パーティションが多いほど、遅くなる可能性があります。

必要のない反復子を再作成しないようにする

すべてのクエリ結果が現在のコンポーネントによって使用される場合は、すべてのページの継続で反復子を作成し直す必要はありません。 改ページが別の呼び出し元コンポーネントによって制御されていない限り、クエリを完全にドレインすることを常にお勧めします。

using (FeedIterator<MyItem> feedIterator = container.GetItemQueryIterator<MyItem>(
    "SELECT * FROM c WHERE c.city = 'Seattle'",
    requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey("Washington")}))
{
    while (feedIterator.HasMoreResults) 
    {
        foreach(MyItem document in await feedIterator.ReadNextAsync())
        {
            // Iterate through documents
        }
    }
}

並列処理の次数を調整する

クエリで、QueryRequestOptions プロパティを調整して、アプリケーションに最適な構成を識別します。これは、(パーティション キー値に対するフィルターなしで) クロス プラットフォーム クエリを実行する場合に特に当てはまります。 MaxConcurrency は、並列タスクの最大数、つまり並列でアクセスされるパーティションの最大数を制御します。 値を -1 に設定すると、SDK は最適なコンカレンシーを決定できます。

using (FeedIterator<MyItem> feedIterator = container.GetItemQueryIterator<MyItem>(
    "SELECT * FROM c WHERE c.city = 'Seattle'",
    requestOptions: new QueryRequestOptions() { 
        PartitionKey = new PartitionKey("Washington"),
        MaxConcurrency = -1 }))
{
    // ...
}

次のことを考えてみましょう。

  • D = 並列タスクの既定の最大数 (= クライアント コンピューター内のプロセッサの合計数)
  • P = ユーザー指定の並列タスクの最大数
  • N = クエリに応答するためにアクセスする必要があるパーティションの数

P のさまざまな値に対する並列クエリの動作に関する影響を次に示します。

  • (P == 0) => シリアル モード
  • (P == 1) => 最大 1 つのタスク
  • (P > 1) => Min (P, N) の並列タスク
  • (P < 1) => Min (N, D) の並列タスク

ページ サイズを調整する

SQL クエリを発行するとき、結果セットが大きすぎると、セグメント化された形式で結果が返されます。

注意

MaxItemCount プロパティは、改ページ位置の自動修正のみに使用しないでください。 主な用途は、1 ページに返される項目の最大数を減らすことで、クエリのパフォーマンスを向上させることです。

また、使用可能な Azure Cosmos DB SDK を使用してページ サイズを設定することもできます。 QueryRequestOptions プロパティでは、列挙操作で返される項目の最大数を設定できます。 MaxItemCount-1 に設定されている場合、SDK はドキュメントのサイズに応じて最適な値を自動的に検索します。 次に例を示します。

using (FeedIterator<MyItem> feedIterator = container.GetItemQueryIterator<MyItem>(
    "SELECT * FROM c WHERE c.city = 'Seattle'",
    requestOptions: new QueryRequestOptions() { 
        PartitionKey = new PartitionKey("Washington"),
        MaxItemCount = 1000}))
{
    // ...
}

クエリが実行されると、結果のデータは TCP パケット内で送信されます。 MaxItemCount に指定した値が小さすぎると、TCP パケット内でデータを送信するために必要なトリップ数が多くなり、パフォーマンスに影響します。 そのため、 MaxItemCount プロパティに設定する値がわからない場合は、 -1 に設定し、SDK で既定値を選択することをお勧めします。

バッファー サイズを調整する

並列クエリは、結果の現在のバッチがクライアントによって処理されている間に結果をプリフェッチするように設計されています。 このプリフェッチは、クエリの全体的な待機時間を改善するのに役立ちます。 QueryRequestOptions プロパティは、プリフェッチされた結果の数を制限します。 MaxBufferedItemCount返される結果の予想される数 (またはそれ以上) に設定して、クエリがプリフェッチの最大メリットを受け取れるようにします。 この値を -1 に設定すると、バッファーする項目の数が自動的に決定されます。

using (FeedIterator<MyItem> feedIterator = container.GetItemQueryIterator<MyItem>(
    "SELECT * FROM c WHERE c.city = 'Seattle'",
    requestOptions: new QueryRequestOptions() { 
        PartitionKey = new PartitionKey("Washington"),
        MaxBufferedItemCount = -1}))
{
    // ...
}

プリフェッチは並列処理の程度に関係なく同じように機能し、すべてのパーティションのデータに対して 1 つのバッファーがあります。

次のステップ

.NET SDK を使用するときのパフォーマンスの詳細を学習するには:

クエリ プランの呼び出しを減らす

クエリを実行するには、クエリ プランを作成する必要があります。 Azure Cosmos DB ゲートウェイに対するネットワーク要求は、クエリ操作の待機時間に追加されます。

クエリ プランのキャッシュを使用する

1 つのパーティションを対象とするクエリのクエリ プランは、クライアントにキャッシュされます。 これにより、最初の呼び出しの後でクエリ プランを取得するためにゲートウェイを呼び出す必要がなくなります。 キャッシュされたクエリ プランで重要なのは、SQL クエリの文字列です。 クエリがパラメーター化されていることを確認する必要があります。 そうでない場合、クエリ プランのキャッシュ参照は、多くの場合、クエリ文字列が呼び出し間で同じになる可能性が低いため、キャッシュ ミスになります。 クエリ プランのキャッシュは、Java SDK バージョン 4.20.0 以降および Spring Data Azure Cosmos DB SDK バージョン 3.13.0 以降では、既定で有効になります。

パラメーター化された単一パーティションのクエリを使用する

setPartitionKeyCosmosQueryRequestOptionsでパーティション キーをスコープとし、集計 (DistinctDCountまたは Group By を含む) を含まないパラメーター化クエリの場合、クエリ プランを回避できます。

CosmosQueryRequestOptions options = new CosmosQueryRequestOptions();
options.setPartitionKey(new PartitionKey("Washington"));

ArrayList<SqlParameter> paramList = new ArrayList<SqlParameter>();
paramList.add(new SqlParameter("@city", "Seattle"));
SqlQuerySpec querySpec = new SqlQuerySpec(
        "SELECT * FROM c WHERE c.city = @city",
        paramList);

//  Sync API
CosmosPagedIterable<MyItem> filteredItems = 
    container.queryItems(querySpec, options, MyItem.class);

//  Async API
CosmosPagedFlux<MyItem> filteredItems = 
    asyncContainer.queryItems(querySpec, options, MyItem.class);

注意

クロスパーティション クエリでは、SDK が既存のすべてのパーティションにアクセスして結果を調べる必要があります。 コンテナーの物理パーティションが多いほど、遅くなる可能性があります。

並列処理の次数を調整する

並列クエリは、複数のパーティションに並列にクエリを実行することによって機能します。 ただし、個々のパーティション分割されたコンテナーからのデータは、クエリごとに順番に取得されます。 そのため、CosmosQueryRequestOptions を使って、値をパーティションの数に設定します。 パーティションの数が不明な場合は、setMaxDegreeOfParallelism を使って大きな数を設定すると、システムが並列処理の最大限度として最小値 (パーティションの数、ユーザー指定の入力) を選びます。 値を -1 に設定すると、SDK は最適なコンカレンシーを決定できます。

クエリに関してデータがすべてのパーティションに均等に分散されている場合は、並列クエリが最適な利点を生み出すことに注意してください。 パーティション分割されたコンテナーが、クエリによって返されるデータのすべてまたは大部分が少数のパーティション (最悪の場合は 1 つのパーティション) に集中するような方法でパーティション分割されている場合、クエリのパフォーマンスが低下します。

CosmosQueryRequestOptions options = new CosmosQueryRequestOptions();
options.setPartitionKey(new PartitionKey("Washington"));
options.setMaxDegreeOfParallelism(-1);

// Define the query

//  Sync API
CosmosPagedIterable<MyItem> filteredItems = 
    container.queryItems(querySpec, options, MyItem.class);

//  Async API
CosmosPagedFlux<MyItem> filteredItems = 
    asyncContainer.queryItems(querySpec, options, MyItem.class);

次のことを考えてみましょう。

  • D = 並列タスクの既定の最大数 (= クライアント コンピューター内のプロセッサの合計数)
  • P = ユーザー指定の並列タスクの最大数
  • N = クエリに応答するためにアクセスする必要があるパーティションの数

P のさまざまな値に対する並列クエリの動作に関する影響を次に示します。

  • (P == 0) => シリアル モード
  • (P == 1) => 最大 1 つのタスク
  • (P > 1) => Min (P, N) の並列タスク
  • (P == -1) => 並列タスク数の Min (N, D)

ページ サイズを調整する

SQL クエリを発行するとき、結果セットが大きすぎると、セグメント化された形式で結果が返されます。 既定では、100 項目または 4 MB (先に達した方) のチャンク単位で結果が返されます。 ページ サイズを大きくすると、必要なラウンド トリップの数が減り、100 を超える項目を返すクエリのパフォーマンスが向上します。 設定する値がわからない場合、通常は 1000 をお勧めします。 ページ サイズが大きくなるとメモリ消費量が増加するため、ワークロードがメモリに依存する場合は、より小さい値を考慮してください。

同期 API では pageSize、非同期 API では iterableByPage()byPage() パラメーターを使って、ページ サイズを定義できます。

//  Sync API
Iterable<FeedResponse<MyItem>> filteredItemsAsPages =
    container.queryItems(querySpec, options, MyItem.class).iterableByPage(continuationToken,pageSize);

for (FeedResponse<MyItem> page : filteredItemsAsPages) {
    for (MyItem item : page.getResults()) {
        //...
    }
}

//  Async API
Flux<FeedResponse<MyItem>> filteredItemsAsPages =
    asyncContainer.queryItems(querySpec, options, MyItem.class).byPage(continuationToken,pageSize);

filteredItemsAsPages.map(page -> {
    for (MyItem item : page.getResults()) {
        //...
    }
}).subscribe();

バッファー サイズを調整する

並列クエリは、結果の現在のバッチがクライアントによって処理されている間に結果をプリフェッチするように設計されています。 プリフェッチは、クエリの全体的な待機時間の向上に役立ちます。 CosmosQueryRequestOptions はプリフェッチされた結果の数を制限します。 プリフェッチを最大化するには、 maxBufferedItemCountpageSize よりも大きい値に設定します (注: メモリ消費量が多くなる場合もあります)。 プリフェッチを最小限に抑えるには、maxBufferedItemCountと等しいpageSizeを設定します。 この値を 0 に設定すると、バッファーに格納する項目の数が自動的に決定されます。

CosmosQueryRequestOptions options = new CosmosQueryRequestOptions();
options.setPartitionKey(new PartitionKey("Washington"));
options.setMaxBufferedItemCount(-1);

// Define the query

//  Sync API
CosmosPagedIterable<MyItem> filteredItems = 
    container.queryItems(querySpec, options, MyItem.class);

//  Async API
CosmosPagedFlux<MyItem> filteredItems = 
    asyncContainer.queryItems(querySpec, options, MyItem.class);

プリフェッチは並列処理の程度に関係なく同じように機能し、すべてのパーティションのデータに対して 1 つのバッファーがあります。

次のステップ

Java SDK を使用するときのパフォーマンスの詳細を学習するには:

クエリ プランの呼び出しを減らす

クエリを実行するには、クエリ プランを作成する必要があります。 Azure Cosmos DB ゲートウェイに対するネットワーク要求は、クエリ操作の待機時間に追加されます。 この要求を削除し、単一パーティション クエリ操作の待機時間を短縮する方法があります。 単一パーティション クエリの場合は、項目のパーティション キー値を指定し、 partition_key 引数として渡します。

items = container.query_items(
        query="SELECT * FROM r where r.city = 'Seattle'",
        partition_key="Washington"
    )

ページ サイズを調整する

SQL クエリを発行するとき、結果セットが大きすぎると、セグメント化された形式で結果が返されます。 max_item_count を使用すると、列挙操作で返される項目の最大数を設定できます。

items = container.query_items(
        query="SELECT * FROM r where r.city = 'Seattle'",
        partition_key="Washington",
        max_item_count=1000
    )

次のステップ

Python SDK for API for NoSQL の使用の詳細については、以下を参照してください。:

クエリ プランの呼び出しを減らす

クエリを実行するには、クエリ プランを作成する必要があります。 Azure Cosmos DB ゲートウェイに対するネットワーク要求は、クエリ操作の待機時間に追加されます。 この要求を削除し、単一パーティション クエリ操作の待機時間を短縮する方法があります。 単一パーティション クエリの場合、クエリを 1 つのパーティションにスコープ設定するには、2 つの方法があります。

パラメーター化されたクエリ式を使用して、クエリ ステートメントでパーティション キーを指定します。 このクエリはプログラムによって SELECT * FROM todo t WHERE t.partitionKey = 'Bikes, Touring Bikes' に構成されます。

// find all items with same categoryId (partitionKey)
const querySpec = {
    query: "select * from products p where p.categoryId=@categoryId",
    parameters: [
        {
            name: "@categoryId",
            value: "Bikes, Touring Bikes"
        }
    ]
};

// Get items 
const { resources } = await container.items.query(querySpec).fetchAll();

for (const item of resources) {
    console.log(`${item.id}: ${item.name}, ${item.sku}`);
}

または、FeedOptions を指定し、引数として渡します。

const querySpec = {
    query: "select * from products p"
};

const { resources } = await container.items.query(querySpec, { partitionKey: "Bikes, Touring Bikes" }).fetchAll();

for (const item of resources) {
    console.log(`${item.id}: ${item.name}, ${item.sku}`);
}

ページ サイズを調整する

SQL クエリを発行するとき、結果セットが大きすぎると、セグメント化された形式で結果が返されます。 maxItemCount を使用すると、列挙操作で返される項目の最大数を設定できます。

const querySpec = {
    query: "select * from products p where p.categoryId=@categoryId",
    parameters: [
        {
            name: "@categoryId",
            value: items[2].categoryId
        }
    ]
};

const { resources } = await container.items.query(querySpec, { maxItemCount: 1000 }).fetchAll();

for (const item of resources) {
    console.log(`${item.id}: ${item.name}, ${item.sku}`);
}

強化されたクエリ制御

Cosmos DB JS SDK バージョン 4.3.0 以降では、 enableQueryControl フラグが導入され、クエリ実行をより詳細に制御でき、要求ユニット (RU) の使用をより柔軟に管理できます。

既定では、 enableQueryControlfalse に設定され、バックエンドに多数の結果が存在する場合、SDK は各 fetchNext 呼び出しが結果の数 maxItemCount 返されることを保証します。 ただし、保証された結果数を満たすために、SDK は 1 回の fetchNext イテレーションでバックエンド パーティションに複数回クエリを実行する場合があります。 これにより、特に結果がパーティション間に散在する場合に、ユーザー制御なしで予期しない待機時間と RU ドレインが発生することがあります。

enableQueryControltrueに設定されている場合、各fetchNext呼び出しは、物理パーティションmaxDegreeOfParallelismまでクエリを実行するようになりました。 結果が見つからない場合、または結果をまだ提供する準備ができていない場合、SDK はバックグラウンドですべてのパーティションを検索し続けるのではなく、空のページを返します。 これにより、ユーザーは、予測可能な待機時間と詳細な RU 消費量データを使用して、クエリの実行をより細かく制御できます。

const options = {
  enableQueryControl: true, // Flag to enable new query pipeline.
  maxItemCount: 100,
  maxDegreeOfParallelism: 6
};

const querySpec = {
    query: "select * from products p where p.categoryId=@categoryId",
    parameters: [
        {
            name: "@categoryId",
            value: items[2].categoryId
        }
    ]
};
const queryIterator = container.items.query(querySpec, options);
// use this iterator to fetch results.

次のステップ

Node.js SDK for API for NoSQL の使用の詳細については、以下を参照してください。