Home Retry 패턴을 적용할 때, 어떤 점을 고려해야 할까?
Post
Cancel

Retry 패턴을 적용할 때, 어떤 점을 고려해야 할까?

1. 글을 작성하게 된 계기


멱등키를 학습하다 Retry를 할 때도 고려할 점이 있다는 것 을 알게 되었고, 이를 정리하기 위해 글을 작성하게 되었습니다.





2. Retry 패턴


Retry 패턴은 네트워크, 데이터베이스 또는 다른 외부 리소스에 대한 연결이 일시적으로 실패할 때 사용할 수 있는 방법입니다. 이는 일시적 오류가 발생했을 경우, 자동으로 요청을 재시도함으로써 애플리케이션의 안정성을 높입니다.

Enable an application to handle transient failures when it tries to connect to a service or network resource, by transparently retrying a failed operation. This can improve the stability of the application.





예를 들어, 소셜 로그인을 통한 회원가입 과정에서, 애플리케이션은 Github에게 먼저 액세스 토큰(Access Token)을 요청합니다. 이때, Github에 많은 요청이 몰려 일시적으로 액세스 토큰을 발급받지 못했다면, 일정 간격을 두고 재시도를 해서 토큰을 발급받는 것입니다.

image







일시적인 오류에는 네트워크 오류, 타임아웃, 임시 리소스 고갈, 제한된 API 호출 배포 중 과 같은 상황이 있습니다. 재시도에 대한 개념은 어느 정도 잡혔을 텐데요, 이제 이를 사용할 때 어떤 고려할 점이 있는지 알아보죠.

  1. 네트워크 오류
  2. 타임아웃
  3. 임시 리소스 고갈
  4. 제한된 API 호출
  5. 배포 중







3. 고려할 점


재시도 패턴을 사용할 때, 멱등성, 불필요한 재시도 하지 않기, 재시도 간격, 로깅 과 같이 몇 가지 고려할 사항이 존재합니다.

  1. 멱등성
  2. 불필요한 재시도 하지 않기
  3. 재시도 간격
  4. 로깅




3-1. 멱등성

먼저 멱등성 을 보장해야 합니다. 멱등성은 특정 연산을 여러 번 하더라도 결과가 바뀌지 않는 성질 을 말합니다.

Idempotence is the property of certain operations in mathematics and computer science whereby they can be applied multiple times without changing the result beyond the initial application.





예를 들어, 아래와 같은 함수가 있을 때, 동일한 x, y 값을 넣으면 항상 같은 결과가 나오는 것 같이요.

1
2
3
4
5
6
7
// x, y 값을 넣으면 항상 일정한 값(x + y)이 반환된다.
fun plus(
    x: Int,
    y: Int
): Int {
    return x + y
}





멱등성을 지키면 동일한 요청을 여러 번 수행해도 결과가 일관성 을 가지므로 예상치 못한 부작용 이 발생하지 않습니다. 우리가 웹 사이트에서 결제 를 할 때, 일시적 오류가 발생해도 두 번 결제가 되지 않죠? 이것도 멱등성을 지키기 때문에 부작용이 발생하지 않는 것입니다.

이를 위해 멱등키(Idempotency Key)를 사용하는데, 관심있다면 해당 링크를 참고해보셔요.







3-2. 불필요한 재시도 하지 않기

불필요한 재시도를 하지 않아야 합니다. 예를 들어, 상품 조회를 했는데 404 NOT_FOUND 응답이 온다면 이를 재시도 할 필요가 있을까요? 없습니다. 존재하지도 않는 상품을 다시 조회하는 것이니까요. 이를 위해서는 재시도 정책 을 명확하게 세우고, 이에 부합하는 경우에만 재시도를 해야 합니다.

A retry policy is required to specify the number of retries, retry interval, and other retry settings.





재시도를 해도 되는 경우는 일시적 오류, 잠재적으로 해결될 수 있는 오류 가 발생했을 때입니다. 여기에는 다음과 같은 상황이 있는데, 위에서 봤던 내용이죠? 😃

  1. 네트워크 오류: 일시적인 네트워크 연결 문제로 인해 발생하는 오류.
  2. 타임아웃: 요청이 지정된 시간 내에 완료되지 않아 발생하는 오류.
  3. 임시 리소스 고갈: 쓰레드/커넥션 풀과 같이 자원이 임시적으로 고갈된 상황.
  4. 제한된 API 호출: RateLimiter로 인해 일정 시간 동안 API 호출이 제한된 상황.
  5. 배포 중: 서비스나 시스템이 업데이트 또는 배포 중일 때 발생하는 일시적 오류.





반면, 재시도를 할 필요가 없는 경우는 클라이언트 오류, 비즈니스 로직 오류, 인증/권한, 자원이 존재하지 않는 경우 등이 있습니다. 이 경우, 재시도를 하더라도 유의미한 결과를 얻을 확률이 낮습니다. 다시 시도한다고 해도 실수가 바로잡히거나, 없는 결과가 생기지는 않으니까요.

  1. 클라이언트 오류
  2. 비즈니스 로직 오류
  3. 인증/권한
  4. 자원이 존재하지 않는 경우







3-3. 재시도 간격

재시도 간격을 적절하게 조절해야 합니다. 여기서 지수 백오프(Exponential Backoff) 전략을 고려할 수 있습니다. 이는 재시도를 할 때, 점진적으로 시간 간격을 늘리며 재시도를 하는 전략입니다.

Exponential backoff is an algorithm that uses feedback to multiplicatively decrease the rate of some process, in order to gradually find an acceptable rate. These algorithms find usage in a wide range of systems and processes, with radio networks and computer networks being particularly notable.





이를 도입하는 이유는 무작정, 연속적으로 하는 재시도는 의미 없거나 네트워크 부담을 더 가중하는 결과를 초래할 수 있기 때문입니다. 예를 들어, API 호출을 했는데 서버가 다운돼서 실패했다고 가정해 보겠습니다. 이때, 0.01초 간격으로 이를 3번 더 재시도한다고 응답할까요? 적어도 다운된 서버가 재 구동될 시간을 줘야겠죠. 따라서, 한 번 실패하면 일정 간격을 두고, 다음 재시도에서는 이전 재시도보다 충분한 시간 간격을 두어 서버가 문제를 해결하고 정상적으로 작동할 수 있도록 합니다.

첫 번째 재시도는 1초 후, 다음 재시도는 1.5초, 2.25초 후와 같이 지수적으로 시간 간격을 늘려가는 것입니다.





AWS에서도 지수 백오프 전략을 사용하는데요, 통계 수치를 보면 시간이 갈수록 API 호출 횟수가 적은 것을 볼 수 있죠? 즉, 재시도 기간을 조금씩 늘리는 것을 통해, 완전히 실패하는 경우가 더 적어진 것을 알 수 있습니다. 자세한 내용에 해당 링크를 참고해 보세요.

image







이 과정에서 지터(Jitter) 도 고려할 수 있는데요, 이는 재시도 간격에 무작위성 을 추가하여, 모든 클라이언트 또는 프로세스가 동시에 같은 리소스나 서비스에 접근하는 것을 방지하는 기법입니다.

In electronics and telecommunications, jitter is the deviation from true periodicity of a presumably periodic signal, often in relation to a reference clock signal. In clock recovery applications it is called timing jitter.





여기에는 다음과 같은 지터 종류가 있습니다.

  1. 무작위 조정(Random Adjustment): 기본적인 지수 백오프 간격에 무작위로 시간을 추가하거나 빼는 방식을 사용합니다. 예를 들어, 재시도 간격은 기본 패턴을 따르되, 각 시도마다 ±0.5초와 같은 무작위 시간을 추가하거나 빼는 것입니다.
  2. 풀 지터(Full Jitter): 재시도 간격을 완전히 무작위화 합니다. 예를 들어, 첫 번째 재시도가 1초 후일 때, 두 번째 재시도는 1초에서 3초 사이에 하는 것입니다.
  3. 이퀄 지터(Equal Jitter): 백오프 시간에 일정한 지터를 추가합니다. 예를 들어, 첫 번째 재시도 후 기다릴 시간이 1초라면, 지터로 ±0.5초를 더해 최소 0.5초에서 최대 1.5초 사이에 두 번째 재시도를 시도합니다.
  4. 디코렐레이티드 지터(Decorrelated Jitter): 각 재시도 시간을 이전 재시도 시간에 의존하지 않고 계산합니다. 즉, 지터를 기반으로 새로운 백오프 시간을 계산하며, 재시도 간격의 독립성을 보장합니다.





AWS는 지수 백오프와 함께 지터도 사용하고 있는데요, 아래는 각 지터를 사용했을 때, 경쟁이 얼마나 감소하는지를 볼 수 있습니다.

image







정리하면 재시도할 때, 일정 간격을 두고 재시도를 해야 하며, 다음 재시도는 이전 재시도 보다 조금 더 후에 해야 합니다. 이 과정에서 지터를 고려할 수 있고요. 또한 재시도를 종료하는 시점이 있어야 합니다. 이를 무한히 반복할 수는 없기 때문입니다. 따라서 최대 n번 재시도 와 같이 재시도를 종료 시점 을 반드시 정합니다.

또는 재시도가 일정 시간을 초과한 경우, 이를 종료하는 방법도 있습니다.







3-4. 로깅

마지막으로 재시도 과정을 로그로 남기는 것이 좋습니다. 어떤 API 호출에서 재시도가 발생했고, 왜 실패했는지, 몇 번 재시도했는지 등을 통해 시스템 평가 지표 를 얻을 수 있기 때문입니다.

1
2
3
4
5
6
INFO ---  [e8491b59-1be4-4d28-8db1-85a1a6cd3b98] Start.
INFO ---  [e8491b59-1be4-4d28-8db1-85a1a6cd3b98] Retry start.
ERROR --- [e8491b59-1be4-4d28-8db1-85a1a6cd3b98] Retry count: 1, message: Github AccessToken을 얻는데 실패했습니다.
ERROR --- [e8491b59-1be4-4d28-8db1-85a1a6cd3b98] Retry count: 2, message: Github AccessToken을 얻는데 실패했습니다.
ERROR --- [e8491b59-1be4-4d28-8db1-85a1a6cd3b98] Retry count: 3, message: Github AccessToken을 얻는데 실패했습니다.
INFO ---  [e8491b59-1be4-4d28-8db1-85a1a6cd3b98] Retry end.







4. 정리


Retry 패턴이 무엇인지, 이를 적용할 때 어떤 점을 고려해야 할지를 살펴보았습니다. 사용법은 간단하지만, 이를 잘 사용하는 것은 꽤 어려운데요, 어떤 점을 주의해야 할지 명확히 알고 잘 사용할 수 있도록 합니다.


This post is licensed under CC BY 4.0 by the author.

ChainedTransactionManager는 왜 Deprecated 됐을까?

EVAL, EVALSHA 명령어의 차이점과 레디스 내부 코드 살펴보기