Azure Cosmos DB Java SDK v4 のパフォーマンスに関するヒント

適用対象: NoSQL

重要

この記事のパフォーマンスに関するヒントは、Azure Cosmos DB Java SDK v4 のみを対象としています。 詳細については、Azure Cosmos DB Java SDK v4 のリリース ノートMaven リポジトリ、Azure Cosmos DB Java SDK v4 のトラブルシューティング ガイドを参照してください。 v4 より前のバージョンを現在使用している場合、v4 へのアップグレードについては、Azure Cosmos DB Java SDK v4 への移行ガイドを参照してください。

Azure Cosmos DB は、高速で柔軟性に優れた分散データベースです。待機時間とスループットが保証されており、シームレスにスケーリングできます。 Azure Cosmos DB でデータベースをスケーリングするために、アーキテクチャを大きく変更したり、複雑なコードを記述したりする必要はありません。 スケールアップとスケールダウンは、API 呼び出しか SDK メソッド呼び出しを 1 回行うだけで簡単に実行できます。 ただし、Azure Cosmos DB にはネットワーク呼び出しによってアクセスするため、Azure Cosmos DB Java SDK v4 を使用するときに最高のパフォーマンスを実現するために、クライアント側の最適化を行うことができます。

データベースのパフォーマンスを向上させる場合は、以下のオプションを検討してください。

ネットワーク

  • パフォーマンスを確保するために同じ Azure リージョン内にクライアントを併置する

可能であれば、Azure Cosmos DB を呼び出すアプリケーションを Azure Cosmos DB データベースと同じリージョンに配置します。 おおよその比較では、Azure Cosmos DB の呼び出しは、同じリージョン内であれば 1 から 2 ミリ秒以内で完了するのに対し、米国西部と米国東部の間では待ち時間が 50 ミリ秒より長くなります。 要求がクライアントから Azure データセンターの境界まで流れるときに使用されるルートに応じて、この待機時間が要求ごとに異なる可能性があります。 最短の待機時間は、プロビジョニングされた Azure Cosmos DB エンドポイントと同じ Azure リージョン内に呼び出し元アプリケーションを配置することによって実現されます。 使用可能なリージョンの一覧については、「 Azure のリージョン」を参照してください。

Azure Cosmos DB 接続ポリシーの図

マルチリージョンの Azure Cosmos DB アカウントとやり取りするアプリでは、併置されたリージョンに要求が確実に送信されるように、優先される場所を構成する必要があります。

高速ネットワークを有効にして待機時間と CPU ジッターを削減する

待ち時間と CPU ジッターを減らしてパフォーマンスを最大限に高めるために、手順に従って Windows (選んで手順を参照) または Linux (選んで手順を参照) の Azure VM で高速ネットワークを有効にすることをお勧めします。

高速ネットワークを使用しない場合、Azure VM と他の Azure リソース間を通過する IO は、VM とそのネットワーク カードの間にあるホストと仮想スイッチを介してルーティングされる可能性があります。 データパスにインラインでホストと仮想スイッチがあると、通信チャネルで待機時間とジッターが増加するだけでなく、VM から CPU サイクルが奪われます。 高速ネットワークでは、VM は仲介者なしで NIC と直接やり取りします。 すべてのネットワーク ポリシーの詳細は、ホストと仮想スイッチをバイパスして、NIC のハードウェアで処理されます。 通常、高速ネットワークを有効にすると、待機時間の短縮とスループットの向上だけでなく、より "一貫した" 待機時間と CPU 使用率の削減が期待できます。

制限事項: 高速ネットワークは、VM の OS でサポートされている必要があり、VM が停止され、割り当てが解除されている場合にのみ有効にすることができます。 Azure Resource Manager を使用して VM をデプロイすることはできません。 App Service では高速ネットワークが有効になっていません。

詳細については、WindowsLinux の手順を参照してください。

直接接続とゲートウェイ接続の構成チューニング

直接およびゲートウェイ モードの接続構成の最適化については、 java sdk v4 の接続構成を調整する 方法を参照してください。

SDK の使用

  • 最新の SDK をインストールする

Azure Cosmos DB SDK は、最適なパフォーマンスを提供するために頻繁に改善されています。 最新の SDK の改善点を確認するには、Azure Cosmos DB SDK を参照してください。

  • アプリケーションの有効期間中はシングルトン Azure Cosmos DB クライアントを使用します

各 Azure Cosmos DB クライアント インスタンスはスレッドセーフであり、効率的な接続管理とアドレスのキャッシュが実行されます。 Azure Cosmos DB クライアントによる効率的な接続管理とパフォーマンスの向上を実現するために、アプリケーションの有効期間中は、Azure Cosmos DB クライアントの単一のインスタンスを使用することを強くお勧めします。

  • アプリケーションに必要な最低の整合性レベルを使用する

CosmosClient を作成するときに、明示的に設定されていない場合に使用される既定の整合性は "Session (セッション) " です。 アプリケーション ロジックで "Session (セッション) " 整合性が必要とされない場合は、"Consistency (整合性) " を "Eventual (最終的) " に設定します。 注: Azure Cosmos DB 変更フィード プロセッサを使用するアプリケーションでは、少なくとも "セッション" 整合性を使用することをお勧めします。

  • 非同期 API を使用してプロビジョニングされたスループットを最大化する

Azure Cosmos DB Java SDK v4 には、同期と非同期の 2 つの API がバンドルされています。 大まかに言うと、非同期 API は SDK 機能を実装し、同期 API は非同期 API のブロッキング呼び出しを行うシン ラッパーです。 これは、非同期のみであった以前の Azure Cosmos DB Async Java SDK v2 や、同期のみで、別の実装を備えていた以前の Azure Cosmos DB Sync Java SDK v2 とは対照的です。

API の選択は、クライアントの初期化中に決定されます。CosmosAsyncClient では非同期 API がサポートされ、CosmosClient では同期 API がサポートされます。

非同期 API では非ブロッキング IO を実装しており、Azure Cosmos DB に要求を発行するときにスループットを最大化することが目標である場合に最適な選択肢です。

各要求への応答でブロックする API が必要な場合や、同期操作がアプリケーションの主要パラダイムである場合は、同期 API の使用が適切な選択と言えます。 たとえば、スループットが重要でないのであれば、マイクロサービス アプリケーションで Azure Cosmos DB にデータを保持する場合に、同期 API を使用できます。

同期 API では要求の応答時間の増加によってスループットが低下し、非同期 API ではハードウェアの全帯域幅の機能を飽和させる可能性があることに注意してください。

同期 API を使用する場合、地理的な併置によって、より高いより一貫したスループットが得られますが (「パフォーマンスを確保するために同じ Azure リージョン内にクライアントを併置する」を参照)、非同期 API で達成可能なスループットを超えることはやはり期待できません。

一部のユーザーは、Azure Cosmos DB Java SDK v4 の非同期 API を実装するために使用される Reactive Streams フレームワークである、Project Reactor に詳しくない場合もあります。 これが懸念される場合は、入門用の「Reactor pattern guide (Reactor パターン ガイド)」を読んでから、こちらの「Introduction to Reactive Programming (Reactive プログラミングの概要)」を読んで理解を深めてください。 非同期インターフェイスで Azure Cosmos DB を既に使用しており、使用していた SDK が Azure Cosmos DB Async Java SDK v2 の場合、ReactiveX/RxJava を使い慣れているかもしれませんが、Project Reactor での変更点がわからない可能性があります。 その場合は、「Reactor vs.RxJava guide (Reactor と RxJava の比較ガイド)」を参照して理解を深めてください。

次のコード スニペットは、それぞれ非同期 API または同期 API 操作で Azure Cosmos DB クライアントを初期化する方法を示しています。

Java SDK V4 (Maven com.azure::azure-cosmos) 非同期 API


CosmosAsyncClient client = new CosmosClientBuilder()
        .endpoint(HOSTNAME)
        .key(MASTERKEY)
        .consistencyLevel(CONSISTENCY)
        .buildAsyncClient();

  • クライアント ワークロードをスケールアウトする

高いスループット レベルでテストを行っている場合、コンピューターが CPU 使用率またはネットワーク使用率の上限に達したことでクライアント アプリケーションがボトルネックになることがあります。 この状態に達しても、クライアント アプリケーションを複数のサーバーにスケールアウトすることで引き続き同じ Azure Cosmos DB アカウントで対応できます。

低待機時間を維持するには、経験則として、特定のサーバーで CPU 使用率が 50% を超えないようにします。

  • 適切なスケジューラを使用する (イベント ループの IO Netty スレッドを盗まない)

Azure Cosmos DB Java SDK の非同期機能は、netty 非ブロッキング IO に基づいています。 SDK は、固定数の IO netty イベント ループ スレッド (コンピューターの CPU コアと同じ数) を使って IO 操作を実行します。 API によって返される Flux は、共有 IO イベント ループ netty スレッドの 1 つに結果を出力します。 したがって、共有 IO イベント ループ netty スレッドをブロックしないことが重要です。 IO イベント ループ netty スレッドで CPU を大量に使う処理を行ったり、操作をブロックしたりすると、デッドロックが発生したり、SDK のスループットが大幅に低下したりする可能性があります。

たとえば、次のコードは、イベント ループ IO netty スレッドで CPU を大量に使う処理を実行します。


Mono<CosmosItemResponse<CustomPOJO>> createItemPub = asyncContainer.createItem(item);
createItemPub.subscribe(
        itemResponse -> {
            //this is executed on eventloop IO netty thread.
            //the eventloop thread is shared and is meant to return back quickly.
            //
            // DON'T do this on eventloop IO netty thread.
            veryCpuIntensiveWork();
        });


結果を受け取った後、イベント ループ IO netty スレッドでは、結果に対する CPU 負荷の高い操作の実行を避ける必要があります。 次に示すように、代わりに独自のスケジューラを用意して、処理を実行するための独自のスレッドを提供できます (import reactor.core.scheduler.Schedulers が必要です)。


Mono<CosmosItemResponse<CustomPOJO>> createItemPub = asyncContainer.createItem(item);
createItemPub
        .publishOn(Schedulers.parallel())
        .subscribe(
                itemResponse -> {
                    //this is now executed on reactor scheduler's parallel thread.
                    //reactor scheduler's parallel thread is meant for CPU intensive work.
                    veryCpuIntensiveWork();
                });

作業の種類に基づいて、作業に適した既存の Reactor Scheduler を使用する必要があります。 「Schedulers」をご覧ください。

Project Reactor のスレッドとスケジューリングのモデルをさらに理解するには、Project Reactor によるこのブログ記事を参照してください。

Azure Cosmos DB Java SDK v4 の詳細については、GitHub の Azure SDK for Java 単一リポジトリの Azure Cosmos DB ディレクトリに関する記事を参照してください。

  • アプリケーションのログ設定を最適化する

さまざまな理由から、高い要求スループットを生成しているスレッドにログを追加する必要があります。 このスレッドによって生成される要求でコンテナーのプロビジョニングされたスループットを完全に飽和させることが目的である場合は、ログの最適化によってパフォーマンスを大幅に向上させることができます。

  • 非同期ロガーを構成する

同期ロガーの待機時間は、要求を生成するスレッドの全体的な待機時間の計算に必ず含まれます。 高パフォーマンスのアプリケーション スレッドからログのオーバーヘッドを分離するために、log4j2 などの非同期ロガーが推奨されます。

  • netty のログを無効にする

netty ライブラリのログは量が多いので、CPU コストが増えないようにオフにする必要があります (構成のサインインを抑制するだけでは不十分な場合があります)。 デバッグ モードではない場合は、netty のログを完全に無効にします。 したがって、Log4j を使って netty からの org.apache.log4j.Category.callAppenders() によって発生する追加の CPU コストを削除するには、コードベースに次の行を追加します。

org.apache.log4j.Logger.getLogger("io.netty").setLevel(org.apache.log4j.Level.OFF);
  • OS の開かれるファイルのリソース制限

Red Hat などの一部の Linux システムには、開かれるファイルの数、したがって合計接続数に上限があります。 現在の制限を確認するには、次のコマンドを実行します。

ulimit -a

構成されている接続プール サイズおよび OS によって開かれる他のファイルのために十分なスペースがあるよう、開かれるファイル (nofile) の値は十分な大きさである必要があります。 大きい接続プール サイズに対応できるように変更できます。

limits.conf ファイルを開きます。

vim /etc/security/limits.conf

次の行を追加または変更します。

* - nofile 100000
  • ポイント書き込みでパーティション キーを指定する

ポイント書き込みのパフォーマンスを向上させるには、次に示すように、ポイント書き込み API 呼び出しで項目のパーティション キーを指定します。

Java SDK V4 (Maven com.azure::azure-cosmos) 非同期 API

asyncContainer.createItem(item,new PartitionKey(pk),new CosmosItemRequestOptions()).block();

項目インスタンスだけを指定した場合は次のようになります。

Java SDK V4 (Maven com.azure::azure-cosmos) 非同期 API

asyncContainer.createItem(item).block();

後者はサポートされていますが、アプリケーションに待機時間が追加されます。SDK が項目を解析し、パーティション キーを抽出する必要があります。

クエリ操作

クエリ操作については、クエリのパフォーマンスのヒントに関するページを参照してください。

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

  • インデックス作成から未使用のパスを除外して書き込みを高速化する

Azure Cosmos DB のインデックス作成ポリシーでは、インデックス作成パス (setIncludedPaths と setExcludedPaths) を使って、インデックス作成に含めたり除外したりするドキュメント パスを指定できます。 インデックス作成コストはインデックス付きの一意のパスの数に直接関係するため、パスのインデックス作成を使用すると、クエリ パターンが事前にわかっているシナリオで書き込みパフォーマンスが向上し、インデックス ストレージを削減できます。 たとえば、次のコードは、ワイルドカード "*" を使用して、ドキュメントのセクション全体 (サブツリーとも呼ばれる) をインデックス作成から追加および除外する方法を示しています。


CosmosContainerProperties containerProperties = new CosmosContainerProperties(containerName, "/lastName");

// Custom indexing policy
IndexingPolicy indexingPolicy = new IndexingPolicy();
indexingPolicy.setIndexingMode(IndexingMode.CONSISTENT);

// Included paths
List<IncludedPath> includedPaths = new ArrayList<>();
includedPaths.add(new IncludedPath("/*"));
indexingPolicy.setIncludedPaths(includedPaths);

// Excluded paths
List<ExcludedPath> excludedPaths = new ArrayList<>();
excludedPaths.add(new ExcludedPath("/name/*"));
indexingPolicy.setExcludedPaths(excludedPaths);

containerProperties.setIndexingPolicy(indexingPolicy);

ThroughputProperties throughputProperties = ThroughputProperties.createManualThroughput(400);

database.createContainerIfNotExists(containerProperties, throughputProperties);
CosmosAsyncContainer containerIfNotExists = database.getContainer(containerName);

詳細については、Azure Cosmos DB インデックス作成ポリシーに関するページをご覧ください。

スループット

  • 測定と調整によって 1 秒あたりの要求ユニットの使用量を削減する

Azure Cosmos DB には、UDF、ストアド プロシージャ、トリガーを使ったリレーショナル クエリや階層クエリなど、さまざまなデータベース操作が用意されています。これらの操作はすべて、データベース コレクション内のドキュメントに対して実行できます。 これらの操作のそれぞれに関連付けられたコストは、操作を完了するために必要な CPU、IO、およびメモリに応じて異なります。 ハードウェア リソースの管理について考える代わりに、各種のデータベース操作を実行しアプリケーション要求を処理するのに必要なリソースに関する単一の測定単位として要求単位 (RU) を考えることができます。

コンテナーごとに設定された要求ユニットの数に基づいて、スループットをプロビジョニングします。 要求単位の消費は、1 秒あたりのレートとして評価されます。 コンテナーのプロビジョニング済み要求ユニット レートを超過したアプリケーションは、レートがそのコンテナーにプロビジョニングされているレベルを下回るまで制限されます。 アプリケーションでより高いスループットが必要になった場合は、追加の要求ユニットをプロビジョニングしてスループットを増やすことができます。

クエリの複雑さは、操作で消費される要求ユニット数に影響します。 述語の数、述語の特性、UDF 数、ソース データ セットのサイズのすべてがクエリ操作のコストに影響します。

操作 (作成、更新、または削除) のオーバーヘッドを測定するには、x-ms-request-charge ヘッダーを調べて、これらの操作で使われる要求ユニット数を測定します。 ResourceResponse<T> または FeedResponse<T> で同等の RequestCharge プロパティを確認することもできます。

Java SDK V4 (Maven com.azure::azure-cosmos) 非同期 API

CosmosItemResponse<CustomPOJO> response = asyncContainer.createItem(item).block();

response.getRequestCharge();

このヘッダーで返される要求の使用量は、プロビジョニングしたスループットの一部です。 たとえば、2000 RU/秒がプロビジョニングされていて、上記のクエリで 1 KB のドキュメントを 1,000 個返した場合、この操作のコストは 1000 になります。 そのため、後続の要求をレート制限する前に、サーバーは 1 秒以内にこのような要求を 2 つだけ受け付けます。 詳細については、要求ユニットに関する記事および要求ユニット計算ツールのページを参照してください。

  • レート制限と大きすぎる要求レートに対処する

クライアントがアカウントの予約済みスループットを超えようとしても、サーバーでパフォーマンスの低下が発生することはなく、予約済みのレベルを超えてスループット容量が使用されることもありません。 サーバーはいち早く RequestRateTooLarge (HTTP 状態コード 429) で要求を終了させ、要求を再試行するまでにユーザーが待機しなければならない時間 (ミリ秒) を示す x-ms-retry-after-ms ヘッダーを返します。

HTTP Status 429,
Status Line: RequestRateTooLarge
x-ms-retry-after-ms :100

SDK はすべてこの応答を暗黙的にキャッチし、サーバーが指定した retry-after ヘッダーを優先して要求を再試行します。 アカウントに複数のクライアントが同時アクセスしている状況でなければ、次回の再試行は成功します。

複数のクライアントが要求レートを常に上回った状態で累積的に動作している場合、現在クライアントによって内部的に 9 に設定されている既定の再試行回数では不十分な可能性があります。この場合、クライアントは状態コード 429 を含む CosmosClientException をアプリケーションにスローします。 デフォルトの再試行カウントは、ThrottlingRetryOptions インスタンス上の setMaxRetryAttemptsOnThrottledRequests() を使って変更できます。 既定では、要求レートを超えて要求が続行されている場合に、30 秒の累積待機時間を過ぎると、状態コード 429 を含む CosmosClientException が返されます。 これは、現在の再試行回数が最大再試行回数 (既定値の 9 またはユーザー定義の値) より少ない場合でも発生します。

自動再試行動作により、ほとんどのアプリケーションの回復性とユーザービリティが向上しますが、パフォーマンス ベンチマークの実行時 (特に待機時間の測定時) に問題が生じることがあります。 実験でサーバー スロットルが発生し、クライアント SDK によって警告なしに再試行が行われると、クライアントが監視する待機時間が急増します。 パフォーマンスの実験中に待機時間が急増するのを回避するには、各操作で返される使用量を測定し、予約済みの要求レートを下回った状態で要求が行われていることを確認します。 詳細については、 要求ユニットに関する記事を参照してください。

  • スループットを向上させるためにサイズの小さいドキュメントに合わせて設計する

特定の操作の要求の使用量 (要求処理コスト) は、ドキュメントのサイズに直接関係します。 サイズの大きいドキュメントの操作は、サイズの小さいドキュメントの操作よりもコストがかかります。 項目のサイズを最大 1 KB 程度にするようにアプリケーションとワークフローを設計するのが理想的です。 待ち時間の影響を受けやすいアプリケーションでは、サイズの大きい項目は避ける必要があります。数 MB のドキュメントはアプリケーションの速度を低下させます。

次のステップ

スケーリングと高パフォーマンスのためのアプリケーションの設計について詳しくは、「Azure Cosmos DB でのパーティション分割とスケーリング」をご覧ください。

Azure Cosmos DB への移行のための容量計画を実行しようとしていますか? 容量計画のために、既存のデータベース クラスターに関する情報を使用できます。