상황 가정
- 사용자 요청을 처리할 때 외부 API 호출이 필요하고, 해당 API(
api/foo
) 호출에 대해 타임아웃이 발생
(client ---> 내 서버 ---X 외부 서버
) - 생각해볼 수 있는 다양한 타임아웃 상황
- 외부 서버에서
api/foo
에 대한 처리가 늦어짐 api/foo
요청이 외부 서버에 늦게 도착api/foo
에 대한 응답이 내 서버에 도착하지 않거나 늦게 도착함- 기타 등등…
- 외부 서버에서
가장 심플한 케이스
api/foo
에 대한 응답 없이 사용자 요청 처리가 가능한 경우
- 특정 데이터는 빈 상태로 조회 응답 내려줄 수 있는 상황
- 또는, 정책적으로 협의가 되어서
api/foo
에 대한 재시도가 필요하지 않은 상황
조금 더 생각이 필요한 케이스
요청을 처리하려면
api/foo
에 대한 응답이 반드시 필요한 경우 (즉, 재시도 필요)
api/foo
가 멱등성이 보장되는 API라면 ?
- 사용자 요청이 처리돼야하는 전체 시간을 고려하여 바로 재시도하거나 또는 사용자에게 “잠시 후 재시도해주세요” 등의 응답을 내려준다.
api/foo
가 멱등성이 보장되지 않는 API라면 ?
- 외부 서버와 협의한 키 값을 통해
api/foo
의 처리 상태를 조회할 수 있는 API(api/bar
) 호출- 즉, 사용자 요청을 처리하는 로직에서
api/foo
호출 전api/bar
를 호출하는 로직이 있어야됨 api/bar
의 처리 상태 확인 후 실패이면 재시도, 성공이면 사용자 요청에 대한 나머지 로직 처리- ‘실패’인 경우 재시도 가능한 경우, 재시도 가능하지 않은 경우까지 구분이 가능하다면 이 부분도 협의하면 좋을 것으로 생각
- 즉, 사용자 요청을 처리하는 로직에서
만약 api/bar
로 조회한 상태가 ‘처리중’이면 ?
- 사용자에게 “처리가 지연되고 있습니다. 잠시만 기다려주세요.” 등의 응답을 내려준다.
- 만약 계속 ‘처리중’인 상황이 지속되는 경우, 외부 서버와 협의된 시간이 지나면 재시도를 한다던지 외부사에 문의한다던지 등의 정책이 필요할 것으로 생각
만약 api/bar
로 조회했는데 값이 없다면 ?
api/foo
요청이 유실됐거나,api/bar
로 조회한 시점보다 늦게 외부 서버에 도착한 경우
- 위에서 살펴본 모든 상황에서 협의가 필요하겠지만, 이 케이스는 반드시 필요하다고 생각
- 협의를 한다면 어떤 부분에 대해 할 수 있을까 ?
- 재시도했을때 멱등성이 보장되도록
- 결과 없음에 대한 요청을 따로 보관
- (특정 시간이 경과하면) 운영팀에서 해당 업체와 연락해서 확인
- 기타 등등..
관련 글 찾아보기
1. zalando 기술 블로그
타임아웃 값 설정 관련
- 라이브러리들 중 비효율적으로 긴 타임아웃이 기본값으로 설정된 경우들도 있다
- 타임아웃을 최대값(p99.9)으로 설정
- 타임아웃을 최대한 길게 설정하여 거의 모든 요청이 응답을 받을 수 있도록 함.
- 결과적으로 False Timeout Rate를 0.1%로 유지.
- 타임아웃을 짧게 설정하고 재시도를 활성화
- 타임아웃을 p99.9보다 낮게 설정해 빠르게 실패를 감지하고, 필요한 경우 요청을 재시도.
- 단, 짧은 타임아웃은 실패를 더 자주 발생시킬 수 있으므로 재시도 정책을 신중히 설계해야 함.
재시도 관련
- 멱등성이 보장되는 API는 안전하게 재시도 가능
- 그렇지 않은 API는 재시도하는 경우 데이터 일관성이 깨지는 등의 이슈가 있을 수 있기 때문에
Idempotency-Key
헤더 등을 이용해서 멱등성을 보장하는 방식으로 작업하는 것을 추천 - 서킷 브레이커를 사용해서 재시도가 너무 많은 부하를 유발하는 것을 방지
- Exponential backoff (지수적 재시도) 방식을 통해 재시도 간격을 늘린다.
- 재시도하기 적합한 상황
- p50과 p99 사이의 차이가 큼
- 즉, 대부분의 요청은 정상적인 속도로 응답하지만, 일부 요청이 비정상적으로 지연되는 경우. 네트워크 일시적 지연 등일 확률이 높기 때문에 다음 요청은 정상 속도로 처리될 가능성이 높음
- p50과 p99 사이의 차이가 큼
- 재시도하기 적합하지 않은 상황
- p50과 p99가 비슷한 경우
- 즉, 특정 요청만 느린 게 아니라, 전체적으로 느린 구조적인 문제가 존재할 확률이 높음
- 재시도보다는 타임아웃 값을 최적화하거나, 시스템 자체를 튜닝하는 것이 더 효과적일 수 있음
- p50과 p99가 비슷한 경우
- 실패 확률을 줄이도록 시스템을 설계하지만 결코 실패하지 않는 시스템을 구축하는 것은 불가능
- Amazon에서는 실패 확률을 허용하고 줄이고, 작은 비율의 실패가 완전한 중단으로 확대되는 것을 방지하도록 시스템을 설계
- 타임아웃을 정하는 좋은 기준은 p99.9와 같은 지표들이 있다.
- 대부분의 경우에는 잘 작동하지만 몇 가지 함정이 있다.
- p99.9와 p50의 지연 값이 비슷한 서비스에서는 적합하지 않음 어떤 서비스는 지연 값의 분포가 매우 빡빡해서 p99.9 값이 p50(평균적인 지연) 값에 근접한 경우가 있습니다. 이런 경우, 타임아웃 값에 약간의 여유(padding)를 더해서, 작은 지연 증가로 인해 대량의 타임아웃이 발생하는 것을 방지합니다.
- 타임아웃에 대한 재시도시 생각해볼 것들
- 일시적인 에러라면 재시도해서 성공할 수도 있겠지만 그게 아니라 부하가 많이 몰린 상황이라면 재시도 요청들이 부하를 가중시켜 상황을 더 악화시킬 수 있다.
- 여러 마이크로 서비스를 거치는 요청이라면, DB에 부하가 더 많이 갈 수 있다.
- 재시도 할만한 가치가 있는지 (4xx 에러, 5xx 에러 등)
- 실패한 모든 요청이 같은 시간에 백오프 후 재시도를 하면, 다시 한 번 과부하나 경쟁이 발생할 수 있음
- 지터는 백오프에 일정한 랜덤성(randomness)을 추가하여 재시도가 시간적으로 분산되도록 만드는 방법입니다.
- 이를 통해 요청이 특정 시간에 몰리지 않게 되어 과부하 문제를 완화할 수 있습니다.
- 더 나아가, 지터는 재시도뿐 아니라 모든 주기적인 작업(timers, periodic jobs 등)에도 적용할 수 있습니다.
3. AWS글
재시도시 중복 처리를 방지하는 방법
- Amazon은 API 계약에 고유한 클라이언트 요청 식별자를 포함시키는 방식을 선호
- 이미 생성된 리소스에 대해 재시도 하는 경우 서비스는 “리소스가 이미 존재한다(ResourceAlreadyExists)”는 응답을 반환할 수 있다.
- 이런 응답은 기본적으로 멱등성을 만족하지만, 클라이언트에게 혼란을 줄 수 있다.
- 즉, 이번 요청으로 인해 리소스가 생성된 것인지, 이전 요청의 결과로 생성된 것인지 알 수 없음
- 따라서 클라이언트는 “리소스가 이미 존재하는” 상황을 별도로 처리해야 하며, 이는 클라이언트 코드의 복잡성을 증가시킴
- 이런 응답은 기본적으로 멱등성을 만족하지만, 클라이언트에게 혼란을 줄 수 있다.
멱등성 설계 방식
- 요청의 파라미터를 해시(hash)로 변환하여 중복 여부를 확인하는 방법이 있음. 하지만, 이 방법이 항상 유효하지 않음.
- 예: EC2 인스턴스 생성 요청이라면? → 동일한 요청이더라도, 사용자는 실제로 두 개의 EC2 인스턴스를 원할 수도 있음.
- 따라서, 각 API 요청에 고유한 요청 식별자(Client Token)를 추가하도록 설계. 즉, 동일한 Client Token을 가진 요청은 동일한 작업으로 간주.
- 요청을 처음 받을 때 Client Token을 기록
- 작업이 원자적으로 실행되도록 보장
- 동일한 Client Token을 가진 요청이 다시 들어오면, 같은 응답을 반환
- 분산 시스템에서 요청이 지연되어 도착하는 경우, 멱등성 처리 과정에서 복잡한 상황이 발생할 수 있음
- 예를 들어 :
- 첫 번째 요청이 리소스를 생성함.
- 두 번째 요청이 재시도되기 전에 누군가 리소스를 삭제함.
- 재시도된 요청이 도착했을 때, 서비스는 어떻게 반응해야 할까?
- AWS의 해결책
- Amazon EC2 RunInstances의 경우, 이러한 상황에서도 최초의 멱등성 계약(idempotent contract)을 존중
- 즉, 삭제된 리소스의 상태를 반영하되, 의미적으로 동일한 응답을 반환함으로써 고객이 혼란스러워하지 않도록 합니다.
- 예를 들어 :
- Client Token 저장 이슈
- 너무 오래 보관하면 이전 요청과의 식별자 충돌 문제가 발생할 가능성이 있음.
- 서비스 및 리소스 유형에 따라 멱등성 정보 유지 기간이 다를 수 있음.
- EC2 인스턴스의 경우, 정보 유지 기간을 해당 리소스의 생애 주기(lifetime) + 추가적인 일정 기간으로 제한.
- 이렇게 하면 늦게 도착하는 요청(재시도 요청 포함)이 도착할 가능성이 있는 기간까지만 정보를 유지하고, 이후에는 제거하여 불필요한 충돌을 방지함.