アプリケーションが大量の要求を Dataverse に送信する必要がある場合は、複数のスレッドを使用して要求を並列で送信することで、はるかに高い合計スループットを実現できます。 Dataverse は複数の同時ユーザーをサポートするように設計されているため、リクエストを並行して送信すると、この強みが活かされます。
注意
プラグイン内での並列要求の送信はサポートされていません。 詳細については、「 プラグインおよびワークフロー アクティビティ内で並列実行を使用しない」を参照してください。
並列処理の最適度 (DOP)
Dataverse は、環境のリソース割り当てを管理します。 多くのライセンスユーザーが頻繁に使用する運用環境には、割り当てられたリソースが多くなります。 割り当てられるサーバーの数と機能は時間の経過と同時に変化する可能性があるため、並列処理の最適な次数は決まっていません。 代わりに、x-ms-dop-hint 応答ヘッダーから返された整数値を使用してください。 この値は、環境に推奨される並列度を示します。
.NET で並列プログラミングを使用する場合、並列処理の既定の次数は、コードを実行しているクライアント上の CPU コアの数によって異なります。 CPU コアの数が環境に最適な数を超えている場合は、送信する要求が多すぎる可能性があります。 ParallelOptions.MaxDegreeOfParallelism プロパティを設定して、同時実行タスクの最大数を定義します。
サービス保護の制限
サービス保護の制限について監視される 3 つのファセットの 1 つは、同時要求の数です。 既定では、この値は 52 ですが、より高い場合があります。 制限を超えると、エラーが返されます。 並列処理の次数を制限するために x-ms-dop-hint 応答ヘッダー値に依存している場合、この制限に達することはめったにありません。 このエラーが発生した場合は、同時実行スレッドの数を減らします。
この制限に達すると、特定のエラーが返されます。
| エラー コード | 16進コード | メッセージ |
|---|---|---|
-2147015898 |
0x80072326 |
Number of concurrent requests exceeded the limit of 52. |
また、サーバー アフィニティを無効にして、環境をサポートするすべてのサーバーに要求を送信することで、このエラーの可能性を減らすこともできます。
サーバー アフィニティ
Azure 上のサービスに接続すると、サービスは応答を含む Cookie を返します。 容量管理によって要求が強制的に別のサーバーに送信されない限り、後続のすべての要求は同じサーバーに移動しようとします。 対話型クライアント アプリケーション (特にブラウザー クライアント) は、サーバーにキャッシュされたデータをアプリケーションで再利用できるため、この Cookie を利用できます。 Web ブラウザーでは常にサーバー アフィニティが有効になっており、無効にすることはできません。
クライアント アプリケーションから要求を並列で送信する場合は、この Cookie を無効にすることでパフォーマンス上の利点を得ることができます。 送信する各要求は、いずれかの対象サーバーにルーティングされます。 この変更により、合計スループットが増加するだけでなく、各制限がサーバーごとに適用されるため、サービス保護の制限の影響を軽減するのにも役立ちます。
次の例は、.NET を使用してサーバー アフィニティを無効にする方法を示しています。
ServiceClient クラスまたは CrmServiceClient クラスを使用している場合は、AppSettings ファイルの App.config ノードに次のコードを追加します。
<add key="PreferConnectionAffinity" value="false" />
EnableAffinityCookie クラスまたは CrmServiceClient クラスを使用して、 プロパティの値を設定することもできます。
このプロパティは、 ServiceClient(ConnectionOptions、Boolean、ConfigurationOptions) コンストラクターと ConfigurationOptions.EnableAffinityCookie プロパティを使用して設定することもできます。
接続の最適化
.NET を使用して要求を並列で送信する場合は、要求が制限されないように既定の設定を変更します。 次の変更を行います。
// Bump up the min threads reserved for this app to ramp connections faster - minWorkerThreads defaults to 4, minIOCP defaults to 4
ThreadPool.SetMinThreads(100, 100);
// Change max connections from .NET to a remote service default: 2
System.Net.ServicePointManager.DefaultConnectionLimit = 65000;
// Turn off the Expect 100 to continue message - 'true' will cause the caller to wait until it round-trip confirms a connection to the server
System.Net.ServicePointManager.Expect100Continue = false;
// Can decrease overall transmission overhead but can cause delay in data packet arrival
System.Net.ServicePointManager.UseNagleAlgorithm = false;
ThreadPool.SetMinThreads
この設定は、新しい要求が入ってくるとスレッド プールがオンデマンドで作成するスレッドの最小数を制御します。 この数に達すると、スレッド プールは、スレッドの作成と破棄を管理するアルゴリズムに切り替わります。
デフォルトでは、スレッドの最小数はプロセッサ数に設定されています。
SetMinThreadsを使用して、スレッドの最小数を増やします。 たとえば、キューに置かれた作業項目やタスクによってスレッド プール スレッドがブロックされる問題を回避するために、一時的に数を増やすことができます。 これらのブロックにより、すべてのワーカーまたはI/O完了スレッドがブロックされる (枯渇) 状況が発生することがあります。 ただし、スレッドの最小数を増やすと、他の方法でパフォーマンスが低下する可能性があります。
使用する数値は、ハードウェアによって異なる場合があります。 従量課金ベースの Azure 関数に使用する数値は、ハイエンド ハードウェアを搭載した専用ホストで実行されるコードよりも低くなります。
詳細: System.Threading.ThreadPool.SetMinThreads
System.Net.ServicePointManager の設定
.NET Framework では、 ServicePointManager は、 ServicePoint クラスのインスタンスの作成、保守、削除に使用する静的クラスです。 これらの設定は、ServiceClient クラス、または CrmServiceClient クラスで使用します。 これらの設定は、.NET Framework で Web API で HttpClient を 使用する場合にも適用する必要があります。 ただし、.NET Core では、代わりに HttpClient の設定をお勧めします。
DefaultConnectionLimit
最終的には、ハードウェアによってこの値が制限されます。 設定が高すぎると、他のメカニズムによって制限されます。 既定値より上にして、送信する同時要求の数と少なくとも同じ数に設定します。
HttpClientで .NET Core を使用すると、HttpClientHandler.MaxConnectionsPerServer プロパティによってこの設定が制御されます。 既定値は int です。MaxValue。
詳細については、以下を参照してください:
- System.Net.ServicePointManager.DefaultConnectionLimit
- .NET Framework 接続プールの制限と新しい .NET 用 Azure SDK
- Web ジョブ用の ServicePointManager の構成
- HttpClientHandler.MaxConnectionsPerServer
Expect100Continue
このプロパティを true に設定すると、クライアントはサーバーへの接続のラウンドトリップ確認を待機します。
HttpClientの場合、HttpRequestHeaders.ExpectContinue の既定値は false です。
詳細については、以下を参照してください:
UseNagleAlgorithm
Nagle アルゴリズムは、データの小さなパケットをバッファリングし、それらを 1 つのパケットとして送信することで、ネットワーク トラフィックを削減します。 このプロセスは"ナグリング" とも呼ばれます。送信されるパケットの数が減り、パケットあたりのオーバーヘッドが減少するため、広く使用されています。 この値を false に設定すると、全体的な送信オーバーヘッドが減少する可能性がありますが、データ パケット到着の遅延が発生する可能性があります。
詳細については、System.Net.ServicePointManager.UseNagleAlgorithmを参照してください。
使用例
次の .NET の例は、 Dataverse でタスク並列ライブラリ (TPL) を使用する方法を示しています。
x-ms-dop-hintまたはの ServiceClient プロパティを使用して、CrmServiceClient応答値にアクセスできます。
Parallel.ForEach を使用するときに ParallelOptions.MaxDegreeOfParallelism を設定するときに、この値を使用します。
これらの例では、EnableAffinityCookie プロパティを false に設定することも示しています。
次の例では、応答の ID 値を GUID の ConcurrentBag に追加します。
ConcurrentBag 順序が問題にならない場合に、オブジェクトのスレッドセーフな順序付けされていないコレクションを提供します。 このメソッドによって返される GUID の順序が、 entityList パラメーターで送信された項目の順序と一致するとは思えません。
.NET 6 以降で ServiceClient を使用する
.NET 6 以降を使用すると、CreateAsync などの ServiceClient に含まれる非同期メソッドで Parallel.ForEachAsync メソッドを使用できます。
/// <summary>
/// Creates records in parallel
/// </summary>
/// <param name="serviceClient">The authenticated ServiceClient instance.</param>
/// <param name="entityList">The list of entities to create.</param>
/// <returns>The id values of the created records.</returns>
static async Task<Guid[]> CreateRecordsInParallel(
ServiceClient serviceClient,
List<Entity> entityList)
{
ConcurrentBag<Guid> ids = new();
// Disable affinity cookie
serviceClient.EnableAffinityCookie = false;
var parallelOptions = new ParallelOptions()
{ MaxDegreeOfParallelism =
serviceClient.RecommendedDegreesOfParallelism };
await Parallel.ForEachAsync(
source: entityList,
parallelOptions: parallelOptions,
async (entity, token) =>
{
ids.Add(await serviceClient.CreateAsync(entity, token));
});
return ids.ToArray();
}
.NET Framework での CrmServiceClient の使用
.NET Framework を使用する場合、CrmServiceClient で使用できる Clone メソッドは、Parallel.ForEach メソッドを使用できるように、Dataverse への既存の接続を複製します。
/// <summary>
/// Creates records in parallel
/// </summary>
/// <param name="crmServiceClient">The authenticated CrmServiceClient instance.</param>
/// <param name="entityList">The list of entities to create.</param>
/// <returns>The id values of the created records.</returns>
static Guid[] CreateRecordsInParallel(
CrmServiceClient crmServiceClient,
List<Entity> entityList)
{
ConcurrentBag<Guid> ids = new ConcurrentBag<Guid>();
// Disable affinity cookie
crmServiceClient.EnableAffinityCookie = false;
Parallel.ForEach(entityList,
new ParallelOptions()
{
MaxDegreeOfParallelism = crmServiceClient.RecommendedDegreesOfParallelism
},
() =>
{
//Clone the CrmServiceClient for each thread
return crmServiceClient.Clone();
},
(entity, loopState, index, threadLocalSvc) =>
{
ids.Add(threadLocalSvc.Create(entity));
return threadLocalSvc;
},
(threadLocalSvc) =>
{
//Dispose the cloned crmServiceClient instance
threadLocalSvc?.Dispose();
}
);
return ids.ToArray();
}
参照
サービス保護の API 制限
Web API WebApiService の並列演算のサンプル (C#)
TPL データフロー コンポーネントを使用した Web API 並列演算のサンプル (C#)
サンプル: CrmServiceClient を使用したタスク並列ライブラリ