대규모에서 .NET 8 API 사용 최적화: 동시성, 배칭, 그리고 복원력 있는 재시도 메커니즘에 대한 기술 심층 분석

발행: (2025년 12월 29일 오전 07:45 GMT+9)
6 min read
원문: Dev.to

Source: Dev.to

소개

외부 API에 의존하는 시스템을 설계할 때, 속도 제한과 같은 잠재적인 확장 병목 현상을 예상하고 완화하는 것이 매우 중요합니다. 이 문서에서는 .NET 8을 사용하여 서드파티 API의 소비량을 초기 500건 이상에서 10,000건 이상으로 성공적으로 확장하고, 처리 시간과 오류율을 크게 감소시킨 기술적 전략을 자세히 설명합니다.

초기 구현

첫 번째 버전은 낮은 볼륨에서는 작동했지만 확장에 실패했습니다:

  • 처리 시간: 10,000 요청당 약 30 분
  • 실패율: 약 50 % (주로 429 Too Many Requests 오류)

작업량은 10,000개의 고유 URL을 순회하는 것으로 구성되었으며, 각각은 개별 API 호출이 필요했습니다. 예:

https://www.example.com/get?id=123

Source:

핵심 최적화

배치 처리

모든 요청을 순차적으로 처리하거나 API에 동시 호출을 폭풍처럼 보내는 대신, 구조화된 배치 전략을 도입했습니다. 요청을 배치로 묶고, 이전 배치가 완료된 후에만 다음 배치를 전송합니다.

동적 지연 시간 계산

속도 제한을 위반하지 않으면서 처리량을 최대화하기 위해 배치 간 지연 시간을 동적으로 계산합니다:

// Delay for the next batch (in seconds)
double delayNext = 10.0 - processingTimePrevious;

processingTimePrevious가 10 초를 초과하면 지연 시간이 0이 되어 다음 배치를 즉시 전송할 수 있습니다.

복원력을 갖춘 제어된 동시성

전역 동시성 제한은 .NET 8 SemaphoreSlim 프리미티브를 사용해 강제합니다:

private static readonly SemaphoreSlim _semaphore = new SemaphoreSlim(50); // max 50 concurrent requests

public async Task SendAsync(HttpRequestMessage request)
{
    await _semaphore.WaitAsync();
    try
    {
        return await _httpClient.SendAsync(request);
    }
    finally
    {
        _semaphore.Release();
    }
}
  • 동시 외부 호출을 50개로 제한합니다.
  • 외부 API에 과부하가 걸리는 것을 방지하고 메모리/스레드 사용을 제어합니다.

비동기 처리 및 큐잉

파이프라인은 async/await를 전체적으로 활용하여 I/O‑바운드 API 호출이 진행되는 동안 스레드 풀이 응답성을 유지하도록 합니다. 요청은 내부적으로 큐에 저장되고, 워커는 세마포어 제한을 준수하며 항목을 디큐합니다.

복원력 정책 (Microsoft.Extensions.Resilience)

재시도 정책

var retryPolicy = Policy
    .Handle()
    .OrResult(r => r.StatusCode == HttpStatusCode.TooManyRequests)
    .WaitAndRetryAsync(
        retryCount: 3,
        sleepDurationProvider: attempt => TimeSpan.FromSeconds(Math.Pow(2, attempt))
    );
  • 일시적인 실패를 지수 백오프와 함께 재시도합니다.
  • 429 응답을 특별히 대상으로 합니다.

회로 차단기 정책

var circuitBreaker = Policy
    .Handle()
    .OrResult(r => r.StatusCode == HttpStatusCode.TooManyRequests)
    .CircuitBreakerAsync(
        handledEventsAllowedBeforeBreaking: 20,
        durationOfBreak: TimeSpan.FromSeconds(30)
    );
  • 20번 연속 실패 후 회로를 열어, 30초 동안 요청을 차단하고 그 후에 다시 트래픽을 허용합니다.

성능 향상

지표최적화 전최적화 후
전체 처리 시간~30 분~15 분 (≈ 50 % 감소)
실패율 (429)~50 %< 5 %
평균 동시 호출 수~5–1050 (제어됨)

결론 및 향후 확장성

.NET 8에서 동시성 제어, 배치 처리, 견고한 재시도 메커니즘을 통합함으로써, 느리던 API 처리 파이프라인을 보다 효율적인 시스템으로 전환하여 처리 시간을 대략 절반으로 단축했습니다. 이번 경험은 다음의 중요성을 강조합니다:

  • 서드파티 API 제한 사항을 이해하기.
  • 확장성을 위해 배치를 활용하기.
  • 재시도 및 서킷 브레이커와 같은 회복 탄력성 패턴 적용하기.

향후 확장성 작업

  • 실시간 지연 메트릭을 기반으로 하는 적응형 배치 크기 조정.
  • Azure Functions 또는 Kubernetes와 같은 분산 처리 도입으로 더 큰 볼륨 처리.
  • 레이트‑리밋 임계값을 모니터링하고 자동으로 튜닝하기.

이러한 최적화는 대규모 API 요청을 처리하기 위한 견고한 기반을 제공하며, 요청량이 지속적으로 증가함에 따라 추가적인 개선 여지를 충분히 남겨 둡니다.

Back to Blog

관련 글

더 보기 »