NoSQL 用 API アカウントで Azure Cosmos DB Java SDK v4 を使用する場合の問題のトラブルシューティング

適用対象: NoSQL

重要

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

この記事では、Azure Cosmos DB for NoSQL アカウントで Azure Cosmos DB Java SDK v4 を使用するときの一般的な問題、回避策、診断手順、およびツールについて説明します。 Azure Cosmos DB Java SDK v4 には、Azure Cosmos DB for NoSQL にアクセスするためのクライアント側の論理表現が用意されています。 この記事では、問題が発生した場合に役立つツールとアプローチについて説明します。

次の一覧から開始します。

  • この記事の一般的な問題と対処法のセクションを確認します。
  • Azure Cosmos DB 中央リポジトリにある Java SDK を参照してください。これは、GitHub のオープン ソースとして利用可能です。 アクティブに監視されている問題セクションがあります。 回避策が既に提出済みの同様の問題がないか確認します。 役立つヒントの 1 つは、*cosmos:v4-item* タグで問題をフィルター処理することです。
  • Azure Cosmos DB Java SDK v4 のパフォーマンスに関するヒントを確認し、推奨される方法に従います。
  • この記事の残りの部分を読みます。解決策が見つからない場合は、 GitHub の問題を提出します。 GitHub の問題にタグを追加するオプションがある場合は、*cosmos:v4-item* タグを追加します。

診断をキャプチャする

Java V4 SDK でのデータベース、コンテナー、項目、クエリの応答には、診断プロパティが含まれます。 このプロパティでは、再試行または一時的な失敗があるかどうかなど、1 つの要求に関連するすべての情報が記録されます。

診断は文字列として返されます。 文字列は、さまざまなシナリオのトラブルシューティングに対応するために改良されるので、バージョンによって異なります。 SDK の各バージョンで、文字列の形式が異なる可能性があります。 破壊的変更を避けるために、文字列を解析しないでください。

次のコード サンプルは、Java V4 SDK を使って診断ログを読み取る方法を示しています。

重要

Java V4 SDK の推奨される最小バージョンを検証し、このバージョン以降を使っているのを確認することをお勧めします。 推奨されるバージョンはこちらで確認できます。

データベース操作

CosmosDatabaseResponse databaseResponse = client.createDatabaseIfNotExists(databaseName);
CosmosDiagnostics diagnostics = databaseResponse.getDiagnostics();
logger.info("Create database diagnostics : {}", diagnostics); 

コンテナー操作

CosmosContainerResponse containerResponse = database.createContainerIfNotExists(containerProperties,
                  throughputProperties);
CosmosDiagnostics diagnostics = containerResponse.getDiagnostics();
logger.info("Create container diagnostics : {}", diagnostics);

項目操作

// Write Item
CosmosItemResponse<Family> item = container.createItem(family, new PartitionKey(family.getLastName()),
                    new CosmosItemRequestOptions());
        
CosmosDiagnostics diagnostics = item.getDiagnostics();
logger.info("Create item diagnostics : {}", diagnostics);
        
// Read Item
CosmosItemResponse<Family> familyCosmosItemResponse = container.readItem(documentId,
                    new PartitionKey(documentLastName), Family.class);
        
CosmosDiagnostics diagnostics = familyCosmosItemResponse.getDiagnostics();
logger.info("Read item diagnostics : {}", diagnostics);

クエリ操作

String sql = "SELECT * FROM c WHERE c.lastName = 'Witherspoon'";
        
CosmosPagedIterable<Family> filteredFamilies = container.queryItems(sql, new CosmosQueryRequestOptions(),
                    Family.class);
        
//  Add handler to capture diagnostics
filteredFamilies = filteredFamilies.handle(familyFeedResponse -> {
    logger.info("Query Item diagnostics through handle : {}", 
    familyFeedResponse.getCosmosDiagnostics());
});
        
//  Or capture diagnostics through iterableByPage() APIs.
filteredFamilies.iterableByPage().forEach(familyFeedResponse -> {
    logger.info("Query item diagnostics through iterableByPage : {}",
    familyFeedResponse.getCosmosDiagnostics());
});

Azure Cosmos DB の例外

try {
  CosmosItemResponse<Family> familyCosmosItemResponse = container.readItem(documentId,
                    new PartitionKey(documentLastName), Family.class);
} catch (CosmosException ex) {
  CosmosDiagnostics diagnostics = ex.getDiagnostics();
  logger.error("Read item failure diagnostics : {}", diagnostics);
}

診断のログ

Java V4 SDK バージョン v4.43.0 以降では、特定の条件を満たしている場合、すべての要求またはエラーに対して Cosmos Diagnostics の自動ログをサポートしています。 アプリケーション開発者は、待機時間 (ポイント操作 (作成、読み取り、置換、アップサート、パッチ) または非ポイント操作 (クエリ、変更フィード、一括、バッチ))、要求の課金、ペイロード サイズのしきい値を定義できます。 要求がこれらの定義されたしきい値を超えると、それらの要求に対する Cosmos の診断が自動的に出力されます。

既定では、Java v4 SDK はこれらの診断を特定の形式で自動的にログします。 ただし、これは、CosmosDiagnosticsHandler インターフェイスを実装し、独自のカスタム診断ハンドラーを提供することで変更できます。

そうすると、これらの CosmosDiagnosticsThresholdsCosmosDiagnosticsHandlerCosmosClientTelemetryConfig オブジェクトで使用できます。これは、同期または非同期クライアントの作成時に CosmosClientBuilder に渡す必要があります。

注: これらの診断しきい値は、ログ、トレース、クライアント テレメトリなどのさまざまな診断に適用されます。

次のコード サンプルは、診断しきい値、カスタム診断ロガーを定義し、クライアント テレメトリ構成を介してそれらを使う方法を示しています。

カスタムの診断しきい値の定義

//  Create diagnostics threshold
CosmosDiagnosticsThresholds cosmosDiagnosticsThresholds = new CosmosDiagnosticsThresholds();
//  These thresholds are for demo purposes
//  NOTE: Do not use the same thresholds for production
cosmosDiagnosticsThresholds.setPayloadSizeThreshold(100_00);
cosmosDiagnosticsThresholds.setPointOperationLatencyThreshold(Duration.ofSeconds(1));
cosmosDiagnosticsThresholds.setNonPointOperationLatencyThreshold(Duration.ofSeconds(5));
cosmosDiagnosticsThresholds.setRequestChargeThreshold(100f);

カスタムの診断ハンドラーの定義

//  By default, DEFAULT_LOGGING_HANDLER can be used
CosmosDiagnosticsHandler cosmosDiagnosticsHandler = CosmosDiagnosticsHandler.DEFAULT_LOGGING_HANDLER;

//  App developers can also define their own diagnostics handler
cosmosDiagnosticsHandler = new CosmosDiagnosticsHandler() {
    @Override
    public void handleDiagnostics(CosmosDiagnosticsContext diagnosticsContext, Context traceContext) {
        logger.info("This is custom diagnostics handler: {}", diagnosticsContext.toJson());
    }
};

CosmosClientTelemetryConfig の定義

//  Create Client Telemetry Config
CosmosClientTelemetryConfig cosmosClientTelemetryConfig =
    new CosmosClientTelemetryConfig();
cosmosClientTelemetryConfig.diagnosticsHandler(cosmosDiagnosticsHandler);
cosmosClientTelemetryConfig.diagnosticsThresholds(cosmosDiagnosticsThresholds);

//  Create sync client
CosmosClient client = new CosmosClientBuilder()
    .endpoint(AccountSettings.HOST)
    .key(AccountSettings.MASTER_KEY)
    .clientTelemetryConfig(cosmosClientTelemetryConfig)
    .buildClient();

再試行の設計

耐障害性のあるアプリケーションの設計方法に関するガイダンスについては、Azure Cosmos DB SDK での耐障害性のあるアプリケーションの設計に関するガイドを参照してください。また SDK の再試行セマンティクスの内容について学習してください。

一般的な問題と対処法

ポータルのメトリックを確認する

ポータルのメトリックを確認すると、クライアント側の問題であるか、サービスに問題があるかの判断に役立ちます。 たとえば、要求が調整されていることを意味する、高いレートのレート制限された要求 (HTTP 状態コード 429) がメトリックに含まれていれば、"要求率が大きすぎる" に関するセクションを確認してください。

ネットワークの問題、Netty の読み取りタイムアウト エラーの発生、低いスループット、長い待機時間

一般的な推奨事項

パフォーマンスを最大限高めるためのヒントを示します。

  • アプリが Azure Cosmos DB アカウントと同じリージョンで実行されていることを確認します。
  • アプリが実行されているホストの CPU 使用率を確認します。 CPU 使用率が 50% 以上の場合は、より高度な構成のホスト上でアプリを実行します。 また、より多数のマシンに負荷を分散することもできます。

接続の帯域幅調整

接続の帯域幅調整は、ホスト マシンの接続制限、または Azure SNAT (PAT) ポート不足のいずれかが原因で発生します。

ホスト マシンの接続制限

一部の Linux システム (Red Hat など) では、開くファイルの最大数に上限があります。 Linux のソケットはファイルとして実装されるため、この数は接続の合計数も制限します。 次のコマンドを実行します。

ulimit -a

開くことができる最大ファイル数 ("nofile" で指定) は、接続プール サイズの 2 倍以上にする必要があります。 詳細については、Azure Cosmos DB Java SDK v4 のパフォーマンスに関するヒントを参照してください。

Azure SNAT (PAT) ポート不足

パブリック IP アドレスを使わずにアプリを Azure Virtual Machines にデプロイした場合、既定では Azure SNAT ポートによって VM 外の任意のエンドポイントへの接続が確立されます。 VM から Azure Cosmos DB エンドポイントへの許可される接続の数は、Azure SNAT 構成によって制限されます。

Azure SNAT ポートが使用されるのは、VM がプライベート IP アドレスを持ち、VM からのプロセスが、パブリック IP アドレスに接続しようとしている場合に限られます。 Azure SNAT の制限を回避するには次の 2 つの回避策があります。

  • Azure Virtual Machines 仮想ネットワークのサブネットに Azure Cosmos DB サービス エンドポイントを追加します。 詳細については、Azure 仮想ネットワーク サービス エンドポイントに関するページを参照してください。

    サービス エンドポイントが有効になると、要求はパブリック IP から Azure Cosmos DB に送信されなくなります。 代わりに、仮想ネットワークとサブネット ID が送信されます。 この変更により、パブリック IP のみが許可された場合はファイアウォール ドロップが発生することがあります。 ファイアウォールを使用している場合、サービス エンドポイントを有効にするときに、Virtual Network ACL を使用してファイアウォールにサブネットを追加します。

  • Azure VM にパブリック IP を割り当てます。

サービスに到達できない - ファイアウォール

ConnectTimeoutException は、SDK がサービスに到達できないことを示します。 直接モードを使用しているときに、次のようなエラーが発生することがあります。

GoneException{error=null, resourceAddress='https://cdb-ms-prod-westus-fd4.documents.azure.com:14940/apps/e41242a5-2d71-5acb-2e00-5e5f744b12de/services/d8aa21a5-340b-21d4-b1a2-4a5333e7ed8a/partitions/ed028254-b613-4c2a-bf3c-14bd5eb64500/replicas/131298754052060051p//', statusCode=410, message=Message: The requested resource is no longer available at the server., getCauseInfo=[class: class io.netty.channel.ConnectTimeoutException, message: connection timed out: cdb-ms-prod-westus-fd4.documents.azure.com/101.13.12.5:14940]

アプリ マシン上でファイアウォールが実行されている場合は、直接モードで使われるポート範囲 10,000 から 20,000 を開きます。 また、「ホスト マシンの接続制限」にも従ってください。

UnknownHostException

UnknownHostException は、Java フレームワークで、影響を受けるマシンで Azure Cosmos DB エンドポイントの DNS エントリを解決できないことを意味します。 マシンで DNS エントリを解決できることを確認するか、カスタム DNS 解決ソフトウェア (VPN、プロキシ、カスタム ソリューションなど) がある場合は、エラーで解決できないことが示されている DNS エンドポイントに適切な構成が含まれていることを確認してください。 エラーがいつも同じである場合は、エラーで説明されているエンドポイントに対して curl コマンドを使用して、マシンの DNS 解決を確認することができます。

HTTP プロキシ

HTTP プロキシを使用する場合は、SDK ConnectionPolicy で構成されている接続の数をサポートできることを確認します。 できない場合、接続の問題が発生します。

コーディング パターンが無効です: Netty IO スレッドのブロック

SDK では、Azure Cosmos DB との通信に Netty IO ライブラリを使用します。 SDK には Async API があり、Netty の非ブロッキング IO API が使用されます。 SDK の IO 作業は IO Netty スレッドで実行されます。 IO Netty スレッドの数は、アプリマシンの CPU コアの数と同じなるように構成されます。

Netty IO スレッドは、非ブロッキング Netty IO 作業のためだけに使用されます。 SDK は、Netty IO スレッドの 1 つの API 呼び出しの結果をアプリのコードに返します。 アプリが Netty スレッドで結果を受け取ってから長時間の処理を実行すると、内部 IO 作業を実行できる十分な IO スレッドが SDK にない可能性があります。 このようなアプリのコーディングでは、スループットが低く、待ち時間が長く、io.netty.handler.timeout.ReadTimeoutException エラーが発生することがあります。 回避策は、操作に時間がかかることがわかっている場合にスレッドを切り替えることです。

たとえば、コンテナーに項目を追加する次のコード スニペットを見てみましょう (データベースとコンテナーの設定に関するガイダンスについては、こちらを参照してください)。Netty スレッドで数ミリ秒以上かかる長時間の作業を実行することがあります。 このような場合、最終的に IO 作業を処理する Netty IO スレッドが存在しない状態になる可能性があります。 その結果、ReadTimeoutException エラーが発生します。

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


//Bad code with read timeout exception

int requestTimeoutInSeconds = 10;

/* ... */

AtomicInteger failureCount = new AtomicInteger();
// Max number of concurrent item inserts is # CPU cores + 1
Flux<Family> familyPub =
        Flux.just(Families.getAndersenFamilyItem(), Families.getAndersenFamilyItem(), Families.getJohnsonFamilyItem());
familyPub.flatMap(family -> {
    return container.createItem(family);
}).flatMap(r -> {
    try {
        // Time-consuming work is, for example,
        // writing to a file, computationally heavy work, or just sleep.
        // Basically, it's anything that takes more than a few milliseconds.
        // Doing such operations on the IO Netty thread
        // without a proper scheduler will cause problems.
        // The subscriber will get a ReadTimeoutException failure.
        TimeUnit.SECONDS.sleep(2 * requestTimeoutInSeconds);
    } catch (Exception e) {
    }
    return Mono.empty();
}).doOnError(Exception.class, exception -> {
    failureCount.incrementAndGet();
}).blockLast();
assert(failureCount.get() > 0);

回避策は、時間のかかる作業を実行するスレッドを変更することです。 アプリのスケジューラのシングルトン インスタンスを定義します。

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

// Have a singleton instance of an executor and a scheduler.
ExecutorService ex  = Executors.newFixedThreadPool(30);
Scheduler customScheduler = Schedulers.fromExecutor(ex);

たとえば、時間のかかる作業 (たとえば、IO をブロックする計算負荷の高い作業) を行う必要がある場合があります。 このような場合は、.publishOn(customScheduler) API を使用して、customScheduler によって提供される worker にスレッドを切り替えます。

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

container.createItem(family)
        .publishOn(customScheduler) // Switches the thread.
        .subscribe(
                // ...
        );

publishOn(customScheduler) を使用することによって、Netty IO スレッドを解放し、カスタム スケジューラによって提供される独自のカスタム スレッドに切り替えます。 この変更によって問題が解決します。 io.netty.handler.timeout.ReadTimeoutException エラーは発生しなくなります。

要求率が大きすぎる

このエラーはサーバー側のエラーです。 これは、プロビジョニングされたスループットを消費したことを示します。 後で再試行してください。 このエラーが頻繁に発生する場合、コレクション スループットを増やすことを検討してください。

  • GetRetryAfterInMilliseconds の間隔でバックオフを実装する

    パフォーマンス テストでは、調整される要求の割合がわずかになるまで負荷を上げる必要があります。 スロットル状態になった場合は、クライアント アプリケーションでバックオフ値を適用し、サーバー側によって指定された再試行間隔を後退させる必要があります。 バックオフにより、再試行までの待ち時間を最小限に抑えることができます。

Java SDK リアクティブ チェーンからのエラー処理

Azure Cosmos DB Java SDK からのエラー処理は、クライアントのアプリケーション ロジックの観点で重要です。 リアクターコア フレームワークによって提供されるさまざまなエラー処理メカニズムがあります。これは、さまざまなシナリオで使用できます。 これらのエラー処理演算子の詳細を理解し、再試行ロジックのシナリオに最適なものを使用することをお勧めします。

重要

onErrorContinue() 演算子は、すべてのシナリオでサポートされているわけではないため、使用しないことをお勧めします。 onErrorContinue() は、リアクティブ チェーンの動作を不明瞭にする可能性のある特殊な演算子であることに注意してください。 これは、ダウンストリームではなくアップストリームで動作する演算子であり、機能するには特定の演算子のサポートを必要とします。スコープは容易に上流に伝播し、これを想定していないライブラリ コードに届く可能性があります (その結果、意図しない動作が発生します)。 この特殊なオペレーターの詳細については、onErrorContinue()資料を参照してください。

Azure Cosmos DB エミュレーターへの接続の失敗

Azure Cosmos DB エミュレーターの HTTPS 証明書が自己署名されています。 SDK でエミュレーターを動作させるには、エミュレーター証明書を Java トラスト ストアにインポートします。 詳細については、Azure Cosmos DB エミュレーター証明書のエクスポートに関するページを参照してください。

依存関係の競合の問題

Azure Cosmos DB Java SDK には、さまざまな依存関係が取り込まれます。一般に、プロジェクトの依存関係ツリーに Azure Cosmos DB Java SDK が依存している古いバージョンのアーティファクトが含まれている場合、アプリケーションの実行時に予期しないエラーが発生する可能性があります。 アプリケーションから予期しない例外がスローされる原因をデバッグしている場合は、依存関係ツリーに 1 つ以上の Azure Cosmos DB Java SDK 依存関係の古いバージョンが誤って取り込まれていないことを改めて確かめるようにお勧めします。

このような問題を回避するには、プロジェクトのどの依存関係で古いバージョンが取り込まれているかを特定し、その古いバージョンに対する推移的な依存関係を排除して、Azure Cosmos DB Java SDK で新しいバージョンを取り込むことができるようにします。

プロジェクトの依存関係のうち、Azure Cosmos DB Java SDK が依存する古いバージョンを取り込むものを特定するには、プロジェクトの pom.xml ファイルに対して次のコマンドを実行します。

mvn dependency:tree

詳細については、maven の依存関係ツリー ガイドを参照してください。

プロジェクトの依存関係のうち、古いバージョンに依存しているものがどれかわかったら、次の例に従って、pom ファイル内でその lib に対する依存関係を変更し、推移的な依存関係を排除します (例は "リアクター コア" が古い依存関係であることを前提としています)。

<dependency>
  <groupId>${groupid-of-lib-which-brings-in-reactor}</groupId>
  <artifactId>${artifactId-of-lib-which-brings-in-reactor}</artifactId>
  <version>${version-of-lib-which-brings-in-reactor}</version>
  <exclusions>
    <exclusion>
      <groupId>io.projectreactor</groupId>
      <artifactId>reactor-core</artifactId>
    </exclusion>
  </exclusions>
</dependency>

詳細については、推移的な依存関係の除外ガイドを参照してください。

クライアント SDK のログ記録を有効にする

Azure Cosmos DB Java SDK v4 では、log4j や logback などの一般的なログ記録フレームワークへのログ記録をサポートするロギング ファサードとして SLF4j が使用されます。

たとえば、ログ記録フレームワークとして log4j を使用する場合は、Java classpath に次の libs を追加します。

<dependency>
  <groupId>org.slf4j</groupId>
  <artifactId>slf4j-log4j12</artifactId>
  <version>${slf4j.version}</version>
</dependency>
<dependency>
  <groupId>log4j</groupId>
  <artifactId>log4j</artifactId>
  <version>${log4j.version}</version>
</dependency>

また log4j の構成を追加します。

# this is a sample log4j configuration

# Set root logger level to INFO and its only appender to A1.
log4j.rootLogger=INFO, A1

log4j.category.com.azure.cosmos=INFO
#log4j.category.io.netty=OFF
#log4j.category.io.projectreactor=OFF
# A1 is set to be a ConsoleAppender.
log4j.appender.A1=org.apache.log4j.ConsoleAppender

# A1 uses PatternLayout.
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=%d %5X{pid} [%t] %-5p %c - %m%n

詳細については、sfl4j ロギング マニュアルを参照してください。

OS ネットワーク統計

netstat コマンドを実行して、ESTABLISHEDCLOSE_WAIT などの状態の接続がいくつあるか把握します。

Linux では、次のコマンドを実行できます。

netstat -nap

Windows では、同じコマンドを異なる引数フラグで実行できます。

netstat -abn

Azure Cosmos DB エンドポイントへの接続だけを示すように結果をフィルター処理します。

ESTABLISHED 状態の Azure Cosmos DB エンドポイントへの接続の数が、構成済みの接続プール サイズより大きくなることはありません。

Azure Cosmos DB エンドポイントへの接続の多くが CLOSE_WAIT 状態である可能性があります。 1,000 を超える可能性があります。 これほど多い数は、接続が確立され、すぐに切断されていることを示します。 このような状況が原因で問題が起こる可能性があります。 詳細については、「一般的な問題と対処法」セクションを参照してください。

一般的なクエリの問題

クエリ メトリックは、クエリがほとんどの時間を費やしている箇所を特定する場合に役立ちます。 クエリ メトリックから、バックエンドとクライアントでどれだけの時間が費やされているかを確認できます。 詳細については、クエリ パフォーマンス ガイドを参照してください。

次のステップ

  • Java SDK v4 のパフォーマンス ガイドラインを確認する
  • Java SDK v4 のベスト プラクティスを確認する