MS Graph API를 사용하여 B2C에서 사용자를 만들기 위해 수백만 명의 사용자를 온-프레미스 AD에서 Azure AD B2C로 마이그레이션하고 있습니다. 이 마이그레이션을 수행하기 위해 .Net Core 3.1 콘솔 응용 프로그램을 작성했습니다. 속도를 높이기 위해 Graph API를 동시에 호출하고 있습니다. 이것은 훌륭하게 작동합니다.
개발하는 동안 Visual Studio 2019에서 실행하는 동안 허용 가능한 성능을 경험했지만 테스트를 위해 Powershell 7의 명령 줄에서 실행하고 있습니다. Powershell에서 HttpClient에 대한 동시 호출 성능은 매우 나쁩니다. Powershell에서 실행할 때 HttpClient가 허용하는 동시 호출 수에는 제한이 있으므로 40-50 개가 넘는 동시 일괄 처리 호출은 쌓이기 시작합니다. 나머지는 차단하면서 40-50 개의 동시 요청을 실행하는 것 같습니다.
비동기 프로그래밍에 대한 지원을 찾고 있지 않습니다. Visual Studio 런타임 동작과 Powershell 명령 줄 런타임 동작의 차이점을 해결하는 데 어려움을 겪는 방법을 찾고 있습니다. Visual Studio의 녹색 화살표 단추에서 릴리스 모드로 실행하면 예상대로 작동합니다. 명령 행에서 실행되지 않습니다.
비동기 호출로 작업 목록을 채우고 Task.WhenAll (tasks)를 기다립니다. 각 호출은 300에서 400 밀리 초 사이입니다. Visual Studio에서 실행할 때 예상대로 작동합니다. 1000 개의 동시 통화를 일괄 처리하고 각각 개별적으로 예상 시간 내에 완료됩니다. 전체 작업 블록은 가장 긴 개별 통화보다 몇 밀리 초 더 오래 걸립니다.
Powershell 명령 줄에서 동일한 빌드를 실행하면 동작이 변경됩니다. 처음 40 ~ 50 번의 호출은 예상되는 300 ~ 400 밀리 초가 걸리지 만 개별 호출 시간은 각각 20 초까지 증가합니다. 전화가 직렬화되고 있다고 생각하므로 한 번에 40 ~ 50 만 실행되고 다른 전화는 대기합니다.
몇 시간의 시행 착오 끝에 HttpClient로 범위를 좁힐 수있었습니다. 문제를 해결하기 위해 Task.Delay (300)를 수행하고 모의 결과를 반환하는 메서드를 사용하여 HttpClient.SendAsync에 대한 호출을 조롱했습니다. 이 경우 콘솔에서 실행하면 Visual Studio에서 실행되는 것과 동일하게 작동합니다.
IHttpClientFactory를 사용하고 있으며 ServicePointManager에서 연결 제한을 조정하려고했습니다.
여기 내 등록 코드가 있습니다.
public static IServiceCollection RegisterHttpClient(this IServiceCollection services, int batchSize)
{
ServicePointManager.DefaultConnectionLimit = batchSize;
ServicePointManager.MaxServicePoints = batchSize;
ServicePointManager.SetTcpKeepAlive(true, 1000, 5000);
services.AddHttpClient(MSGraphRequestManager.HttpClientName, c =>
{
c.Timeout = TimeSpan.FromSeconds(360);
c.DefaultRequestHeaders.Add("User-Agent", "xxxxxxxxxxxx");
})
.ConfigurePrimaryHttpMessageHandler(() => new DefaultHttpClientHandler(batchSize));
return services;
}
다음은 DefaultHttpClientHandler입니다.
internal class DefaultHttpClientHandler : HttpClientHandler
{
public DefaultHttpClientHandler(int maxConnections)
{
this.MaxConnectionsPerServer = maxConnections;
this.UseProxy = false;
this.AutomaticDecompression = System.Net.DecompressionMethods.GZip | System.Net.DecompressionMethods.Deflate;
}
}
다음은 작업을 설정하는 코드입니다.
var timer = Stopwatch.StartNew();
var tasks = new Task<(UpsertUserResult, TimeSpan)>[users.Length];
for (var i = 0; i < users.Length; ++i)
{
tasks[i] = this.CreateUserAsync(users[i]);
}
var results = await Task.WhenAll(tasks);
timer.Stop();
HttpClient를 조롱 한 방법은 다음과 같습니다.
var httpClient = this.httpClientFactory.CreateClient(HttpClientName);
#if use_http
using var response = await httpClient.SendAsync(request);
#else
await Task.Delay(300);
var graphUser = new User { Id = "mockid" };
using var response = new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(JsonConvert.SerializeObject(graphUser)) };
#endif
var responseContent = await response.Content.ReadAsStringAsync();
다음은 500 개의 동시 요청을 사용하여 GraphAPI를 통해 생성 한 10k B2C 사용자에 대한 메트릭입니다. TCP 연결이 작성되고 있기 때문에 처음 500 개의 요청이 평소보다 오래 걸립니다.
다음은 콘솔 실행 메트릭에 대한 링크 입니다.
다음은 Visual Studio 실행 메트릭에 대한 링크 입니다.
VS 실행 메트릭의 차단 시간은 테스트 실행을 위해 가능한 한 문제가있는 코드를 격리하기 위해 모든 동기 파일 액세스를 프로세스의 끝으로 이동했기 때문에이 게시물에서 언급 한 것과 다릅니다.
프로젝트는 .Net Core 3.1을 사용하여 컴파일됩니다. Visual Studio 2019 16.4.5를 사용하고 있습니다.