のバージョン 4 (V4) AWS SDK for .NET がリリースされました。
重要な変更とアプリケーションの移行については、「移行トピック」を参照してください。
翻訳は機械翻訳により提供されています。提供された翻訳内容と英語版の間で齟齬、不一致または矛盾がある場合、英語版が優先します。
のパフォーマンスのベストプラクティス AWS SDK for .NET
クライアントの作成方法、レスポンスの処理方法、アプリケーションの設定方法は、スループット、レイテンシー、メモリ使用量に大きな影響を与えます。このトピックでは、アプリケーションを効率的かつ確実に実行するのに役立つ使用パターンと設定パターンについて説明します。これらのパターンは、高負荷またはコンテナやサーバーレス関数などのリソースに制約のある環境で最も重要です。これらのプラクティスに従うことで、パフォーマンスが向上し、応答の遅延、ハング、メモリ使用量の増加などの一般的な問題を防ぐことができます。
最も影響の大きいプラクティスは次のとおりです。
-
リクエストごとに作成せずに、1 つの存続期間の長いサービスクライアントを再利用します。
-
ネットワーク接続が接続プールに解放されるように、レスポンスとストリームを破棄します。
-
async/await を正しく使用し、非同期 SDK 呼び出しをブロックしないでください。
-
AWS Lambda や Amazon ECS などの制約のある環境に .NET ガベージコレクションを設定します。
-
高スループットで HTTP 接続と接続制限を管理します。
開始する前に、環境をセットアップし、プロジェクトを設定したことを確認してください。
1 つの存続期間の長いサービスクライアントを再利用する
AmazonS3Client や AmazonDynamoDBClient などのサービスクライアントはスレッドセーフで、構築コストが比較的高く、存続時間が長くなります。クライアントを構築すると、リージョンとエンドポイントの情報が解決され、基盤となる HTTP インフラストラクチャが確立されます。サービスごとに 1 つのクライアントを作成し、アプリケーションの存続期間中再利用します。複数のリージョンを呼び出す場合は、リージョンごとに個別のクライアントを作成します。
警告
リクエストごと、またはループ内で新しいサービスクライアントを作成しないでください。クライアントを作成すると、基盤となる HTTP インフラストラクチャが繰り返し解約され、測定可能なレイテンシーが追加されます。また、ソケットやハンドルを使い果たし、ロード中にリクエストが失敗したりハングしたりする可能性があります。
依存関係インジェクションを使用するアプリケーションでは、クライアントをシングルトンとして登録します。AWSSDK.Extensions.NETCore.Setup NuGet パッケージのAddAWSService拡張メソッドは、デフォルトの有効期間が のクライアントを登録しますServiceLifetime.Singleton。クライアントは最初にリクエストされたときに作成され、同じインスタンスがプロセスの存続期間にわたって再利用されます。依存関係インジェクションでサービスを登録し、設定からオプションを読み取る方法の詳細については、 AWS 「」を参照してくださいAWSSDK.Extensions.NETCore.Setup および IConfiguration。
クライアントを手動でシングルトンとして登録することもできます。
builder.Services.AddSingleton<IAmazonS3>(_ => new AmazonS3Client());
注記
はデフォルトでクライアントをシングルトンとしてAddAWSService登録するため、 が提供するクライアントは破棄しないでください。デフォルト以外の有効期間が必要な場合は、 のオプションlifetimeパラメータに別のServiceLifetime値を渡しますAddAWSService。
クライアントを再利用しても、オペレーションごとのレスポンスの破棄を停止すべきではありません。アプリケーションの存続中はクライアントを再利用しますが、「」で説明されているように、個々のオペレーションが返すレスポンスとストリームは引き続き破棄しますレスポンスとストリームを破棄する。
レスポンスとストリームを破棄して接続を解放する
一部の SDK レスポンスオブジェクトには、ライブネットワークストリームがあります。最も一般的な例は GetObjectResponse です。GetObjectResponse は、 ResponseStreamプロパティを介してオブジェクトのコンテンツを実装IDisposableして公開します。レスポンスは、ストリームが完全に読み込まれるか、レスポンスが破棄されるまで、開いている HTTP 接続を保持します。これらのレスポンスをリークする場合 (各結果を破棄せずにループGetObjectで を呼び出すなど)、接続プールが使い果たされ、次の呼び出しがブロックされるまで、オープン接続が蓄積されます。これは、N 番目のオブジェクトで「ランダムにハングする」または停止するダウンロードの報告の背後にある原因です。
常にストリーミングレスポンスをusingステートメントにラップし、ストリームをすみやかに読み取りまたはコピーします。
using Amazon.S3; using Amazon.S3.Model; // s3Client is a reused, long-lived client. var request = new GetObjectRequest { BucketName = bucketName, Key = key }; using var response = await s3Client.GetObjectAsync(request); await response.WriteResponseStreamToFileAsync(filePath, append: false, CancellationToken.None);
レスポンスを破棄して破棄することは同等ResponseStreamです。どちらか一方が基盤となるネットワークストリームを閉じ、プールへの接続を返します。
ヒント
サイズ、最終変更時間、コンテンツタイプ、ETag などのオブジェクトメタデータのみが必要な場合は、 GetObjectMetadataAsyncの代わりに GetObjectMetadataまたは を呼び出しますGetObject。メタデータオペレーションは HTTP HEADリクエストを発行し、オブジェクト本文を転送しないため、管理するコンテンツストリームはありません。
var metadata = await s3Client.GetObjectMetadataAsync(bucketName, key); Console.WriteLine($"Size: {metadata.ContentLength} bytes");
警告
などのレスポンスおよびストリームタイプはファイナライザーを実装GetObjectResponseしていないため、ガベージコレクションを使用して接続を解放することはできません。レスポンスとストリームは、 usingまたは明示的なDispose呼び出しを使用して決定的に破棄する必要があります。
async/await を正しく使用する
最新の .NET では、 のサービスオペレーション AWS SDK for .NET は非同期であり、 を返しますTask。.NET Framework では、同期メソッドも存在しますが、非同期呼び出しはより適切にスケールされるため、推奨されます。でオペレーションを消費しawait、コードのすべてのasyncレイヤーをエントリポイントまで伝達します。SDK を使用した非同期プログラミングの詳細については、「」を参照してください非同期プログラミング。
警告
.Result、、.Wait()または で非同期 SDK 呼び出しをブロックしないでください.GetAwaiter().GetResult()。このsync-over-asyncパターンは、ハングするアプリケーションの一般的な原因です。
-
ロード中、ブロック呼び出しはスレッドプールが増大するよりも速くスレッドを消費するため、継続を実行できません。このスレッドプールのスタベーションは無期限のハングとして表示され、最新の .NET ( ASP.NET Coreにデフォルト がない場合
SynchronizationContext) の主要な障害モードです。 -
一部のコンテキストは
SynchronizationContext、従来の ASP.NET、、、Blazor WebAssembly、および .NET Framework Windows Forms WPFアプリケーションなどの をキャプチャします。これらのコンテキストでは、継続に同じスレッドが必要なときに呼び出し元のスレッドをブロックするとデッドロックが発生します。SDK のオペレーションはConfigureAwait(false)内部的に を使用するため、その継続をキャプチャされたコンテキストに投稿しません。デッドロックは、それをキャプチャするコールチェーンの他の場所にあるasyncコードから発生します。await全体で で SDK 呼び出しを使用すると、問題を完全に回避できます。
次のメソッドは、非同期呼び出しをブロックし、スレッドプールをデッドロックまたはスタベーションする可能性があります。
// Anti-pattern: do not do this. public GetObjectResponse Get(GetObjectRequest request) { return s3Client.GetObjectAsync(request).Result; }
代わりに、 メソッドasyncと 呼び出しawaitを行います。
public async Task<GetObjectResponse> GetAsync(GetObjectRequest request) { return await s3Client.GetObjectAsync(request); }
非同期コードに関するその他の推奨事項:
-
すべてのオペレーション
CancellationTokenに を渡して、低速または停止した呼び出しをハングアップせずにキャンセルできるようにします。詳細については、「タイムアウトに CancellationToken パラメータを使用する」を参照してください。 -
空の
catchブロックで例外を嚥下しないでください。これにより、実際の障害が非表示になり、ハングをエラーと区別できなくなります。特定の例外をキャッチしてログに記録します。 -
キャッチされた例外を再スローする場合は、元のスタックトレースが保持
throw ex;されるように、throw;ではなく を使用します。 -
同期境界から を呼び出す必要がある場合は、それを最後の手段として扱い、ブロック呼び出しをデフォルトのパターンにするのではなく、キャプチャされたコンテキストから作業を分離します。
AWS Lambda および Amazon ECS の .NET ガベージコレクションを設定する
コンテナに「メモリリーク」があるように見える場合、.NET ガベージコレクター (GC) は再利用のために再利用されたメモリを保持している可能性があります。その結果、マネージドヒープが増加していなくても、プロセスメモリは高く安定しているように見えます。さらに、GC はコンテナのメモリ制限 (cgroup 制限) を自動的に検出しません。制約のある環境では、ヒープはコンテナの制限ではなくホストのメモリに向かって大きくなる可能性があります。これにより、 OutOfMemoryExceptionまたはコンテナが終了する可能性があります。
制約のある環境で GC をうまく機能させるには:
-
GC が cgroup 制限に従うようにコンテナに明示的なメモリ制限を設定するか、
DOTNET_GCHeapHardLimit(絶対バイト値、16 進数) またはDOTNET_GCHeapHardLimitPercent環境変数を設定してマネージドヒープを上限にします。報告されたいくつかの Amazon ECS out-of-memoryのケースでは、ハードメモリ制限を設定するとクラッシュが解決されました。 -
小規模ホスト AWS Lambda と Amazon ECS ホストでは、同時 (バックグラウンド) ガベージコレクションを無効にして、コレクターが追加のメモリを予約しないようにすることを検討してください。たとえば、
DOTNET_gcConcurrent環境変数を に設定するか0、プロジェクトファイル<ConcurrentGarbageCollection>false</ConcurrentGarbageCollection>で を設定します。 -
同時実行数を制限します。大規模なコレクション
Task.WhenAllを呼び出すなど、多数のオペレーションを一度に起動すると、プロセスメモリが拡張され、接続プールが枯渇する可能性があります。代わりに並列処理の度合いを設定します。たとえば、この無制限のパターンは避けてください。// Anti-pattern: starts one task per item with no limit. await Task.WhenAll(keys.Select(key => s3Client.GetObjectMetadataAsync(bucket, key)));代わりに、 との同時実行数の上限を設定します
Parallel.ForEachAsync。var options = new ParallelOptions { MaxDegreeOfParallelism = 10 }; await Parallel.ForEachAsync(keys, options, async (key, token) => { await s3Client.GetObjectMetadataAsync(bucket, key, token); });
これらの設定の詳細については、learn.microsoft.com のガベージコレクションのランタイム設定オプション
HTTP 接続と接続制限の管理
高スループットでは、2 つの接続関連の問題が一般的です。1 つ目は、短時間接続を開く回数が多すぎることです。これにより、エフェメラルポートが枯渇し、ソケットが に残りTIME_WAIT、TCP および TLS ハンドシェイクのレイテンシーが追加されます。2 つ目は、利用可能な接続が少なすぎて、並列処理がボトルネックになることです。単一の存続期間の長いクライアント (「」を参照サービスクライアントを再利用する) を再利用することは、正常な接続プーリングの基盤です。プーリングは再利用されるクライアントに依存するためです。
エンドポイントあたりの同時接続数を調整するには、クライアント設定で MaxConnectionsPerServerプロパティを設定します。このプロパティが null (デフォルト) の場合、基盤となるHttpClientHandlerデフォルトが適用され、最新の .NET では実質的に無制限です。同じエンドポイントへの多数の同時リクエストが接続でボトルネックになっている場合にのみ、それを増やします。適切な開始点は、エンドポイントごとに予想される同時リクエストのピーク数です。ワークロードよりもはるかに高く設定すると、スループットを向上させることなくソケットが無駄になります。
using Amazon.S3; var config = new AmazonS3Config { MaxConnectionsPerServer = 50 }; var s3Client = new AmazonS3Client(config);
すでに依存関係インジェクションを使用している場合は、 を使用してクライアントを設定しますAWSSDK.Extensions.NETCore.Setup。これは、DI を使用するか、複数のサービスクライアントを登録する場合に推奨されるアプローチです。設定が一元化され、クライアントの挿入とテストが容易になります。コードではなく、アプリケーションの設定から設定値を設定できます。詳細については、「AWSSDK.Extensions.NETCore.Setup および IConfiguration」を参照してください。
最後に、独自の並列処理をバインドして、接続制限で許可されている以上の同時オペレーションを開始しないようにします。たとえば、接続制限に合わせてSemaphoreSlimサイズが のゲート呼び出しがあるとします。
var throttle = new SemaphoreSlim(50); // match MaxConnectionsPerServer await throttle.WaitAsync(token); try { await s3Client.GetObjectAsync(request, token); } finally { throttle.Release(); }
タイムアウトと再試行を設定する
タイムアウトと再試行は、認識されたパフォーマンスに直接影響します。Timeout 値が多すぎると、リクエストブロックが長時間停止されます。サービスがすでにスロットリングエラーを返している場合、積極的な再試行ポリシーはより多くのリクエストを追加し、スロットリングを悪化させる可能性があります。レイテンシーと障害に対するアプリケーションの許容度に合った再試行ポリシーを選択し、ハングを隠す方法で再試行するのではなく、真の例外が伝播されるようにします。
注記
Timeout プロパティは非同期呼び出しには影響しません。非同期呼び出しを使用している場合は、タイムアウトに CancellationToken パラメータを使用する代わりに「」を参照してください。
再試行モード、、MaxErrorRetryおよび Timeoutプロパティ (ReadWriteTimeout は .NET Framework にのみ適用されます) の詳細と設定方法の例については、「」を参照してください再試行とタイムアウト。
ストリーミングとラージオブジェクト転送の最適化 (Amazon S3)
大きなオブジェクト、または多数のオブジェクトをアップロードおよびダウンロードするには、Amazon.S3.Transfer名前空間で TransferUtility クラスを使用します。マルチパート転送を使用して並行してアップロードとダウンロードを行い、ストリーム、パート、接続を管理します。これは単一ストリーム転送よりも高速であり、大きなオブジェクトを移動するために推奨される方法です。
-
並列マルチパートダウンロード。SDK の以前のバージョンでは、パートを並行してダウンロードするのではなく、オブジェクトを単一のストリームとしてダウンロードしました。
AWSSDK.S3バージョン 4.0.17 以降、TransferUtilityは、DownloadWithResponseAsync、、OpenStreamWithResponseAsyncおよびDownloadDirectoryWithResponseAsyncメソッドを通じてマルチパート (並列) ダウンロードを提供します。を使用するとOpenStreamWithResponseAsync、返されたストリームを消費している間、オブジェクトの部分はメモリにバッファされます。TransferUtilityOpenStreamRequest のMaxInMemoryPartsプロパティでバッファされるパートの数を制御します。ほとんどの転送では、 を優先しますTransferUtility。特定の範囲またはカスタム並列処理スキームが必要な場合にのみ、GetObjectRequest のByteRangeプロパティを使用してバイト範囲を自分でダウンロードします。詳細については、 デベロッパーツールブログのAWS 「 SDK for .NET Transfer Manager のマルチパートダウンロードサポートの紹介」を参照してください。 AWS -
アップロードのコンテンツの長さ。Amazon S3 には既知のコンテンツの長さ
PUTが必要であり、デフォルトでは SDK はリクエスト本文のチェックサムを計算します。長さがわかっていてストリームがシーク可能である場合、SDK はオブジェクト全体をメモリにバッファすることなくこれを行うことができます。は、必要に応じてバッファリングすることで、シーク不可のストリームTransferUtilityを処理します。代わりに、シーク不可のストリーム ( の raw リクエスト本文などASP.NET Core) でPutObjectAsync直接 を呼び出すと、リクエストが失敗する可能性があります。シーク可能なストリームを提供するか、コンテンツの長さを明示的に設定するか、SDK がチェックサムを計算する方法を設定します。詳細については、「SDK およびツールリファレンスガイド」の「データ整合性保護 AWS SDKs 」を参照してください。 -
パートのサイズ設定。Amazon S3 では、マルチパートアップロードごとに最大 10,000 個のパートを使用できます。合計長がわかっている場合、SDK は、この制限内にとどまるパートサイズを自動的に計算します。主に、長さが事前にわからないストリームに
PartSize自分で設定する必要があります。そうしないと、小さなデフォルトパートが非常に大きなアップロードの制限を超える可能性があります。パートサイズを大きくすると、パートごとのオーバーヘッドも削減され、パートあたりのメモリが増えます。
パフォーマンスの問題の診断
スローダウン、ハング、または明らかなリークを調査すると、次のシグナルが原因をすばやく見つけるのに役立ちます。
-
CLOSE_WAITまたはTIME_WAIT状態のソケット数の増加 ( で表示netstat) は、未処理のレスポンスまたはリクエストごとに作成および破棄されるクライアントのフィンガープリントです。「レスポンスとストリームを破棄する」および「サービスクライアントを再利用する」を参照してください。 -
リクエストメトリクスとレスポンスのログ記録を有効にして、リクエストが実際にディスパッチされていることを確認し、レイテンシーを測定します。サービスクライアントを作成する前に、AWSConfigs で LoggingConfig オブジェクトのプロパティを設定します。クライアントは、構築
LogMetrics時に などの設定をキャプチャします。using Amazon; AWSConfigs.LoggingConfig.LogMetrics = true; AWSConfigs.LoggingConfig.LogResponses = ResponseLoggingOption.OnError; -
メモリを調査するときは、全プロセスメモリをマネージドヒープと区別します。高いが安定したプロセスメモリは、多くの場合、リークではなく再利用されたメモリを保持する GC です。「ガベージコレクションを設定する」を参照してください。