네트워크 호출이나 외부 서비스 연동과 같이 일시적인 장애가 발생할 수 있는 작업에는 재시도(Retry) 로직을 적용하는 것이 좋습니다. 재시도 로직은 작업이 실패했을 때 즉시 포기하지 않고, 일정 횟수만큼 자동으로 다시 시도함으로써 애플리케이션의 안정성과 복원력(resilience)을 높여줍니다.
재시도 로직 구현 시 핵심 원칙
재시도 로직은 단순히 반복문을 돌리는 것 이상으로 신중하게 설계해야 합니다. 다음 두 가지 원칙을 반드시 기억하세요.
1. 실패 작업의 전체 맥락을 아는 곳에만 적용
재시도 로직은 해당 작업이 실패한 전체 맥락(full context)을 파악할 수 있는 위치에서만 구현해야 합니다. 예를 들어, 쓰기 작업이 실제로 성공했는지 여부를 알 수 없는 상황에서 무조건 재시도하면 데이터 중복이나 부작용이 발생할 수 있습니다.
2. 모든 연결 실패는 반드시 로깅
재시도를 유발하는 모든 연결 실패(connectivity failure)는 반드시 로그로 남겨야 합니다. 이를 통해 애플리케이션, 서비스 또는 리소스의 근본적인 문제를 추적하고 원인을 분석할 수 있습니다.
구현 예제
아래 예제는 RetryHelper라는 정적 클래스를 통해 지정된 횟수만큼 작업을 재시도하는 간단한 패턴을 보여줍니다. 각 시도 사이에는 설정된 지연 시간(여기서는 2초)만큼 대기합니다.
class Program{
public static void Main(){
HttpClient client = new HttpClient();
dynamic res = null;
var retryAttempts = 3;
var delay = TimeSpan.FromSeconds(2);
RetryHelper.Retry(retryAttempts, delay, () =>{
res = client.GetAsync("https://example22.com/api/cycles/1");
});
Console.ReadLine();
}
}
public static class RetryHelper{
public static void Retry(int times, TimeSpan delay, Action operation){
var attempts = 0;
do{
try{
attempts++;
System.Console.WriteLine(attempts);
operation();
break;
}
catch (Exception ex){
if (attempts == times)
throw;
Task.Delay(delay).Wait();
}
} while (true);
}
}코드 동작 방식
- 재시도 횟수와 지연 시간 설정:
retryAttempts = 3은 최대 3번까지 시도한다는 의미이며,delay는 각 시도 사이의 대기 시간입니다. - 작업 실행:
Action델리게이트로 전달된 HTTP GET 요청이 실행됩니다. - 예외 처리: 작업이 성공하면
break로 루프를 종료하고, 실패하면 마지막 시도인 경우 예외를 다시 던져(throw) 호출자에게 알립니다. - 대기 후 재시도: 마지막 시도가 아니라면
Task.Delay(delay).Wait()로 지정된 시간만큼 기다린 뒤 다시 시도합니다.
추가 고려 사항
실제 프로덕션 환경에서는 위와 같은 직접 구현보다 Polly 같은 검증된 탄력성(resilience) 라이브러리를 사용하는 것도 좋은 선택입니다. Polly는 지수 백오프(exponential backoff), 회로 차단기(circuit breaker), 타임아웃 등 다양한 재시도 정책을 손쉽게 구성할 수 있게 해줍니다. 또한 비동기 메서드(async/await)를 사용하는 경우 스레드를 차단하는 .Wait() 대신 비동기 방식으로 대기하는 것이 성능 면에서 바람직합니다.