본문 바로가기

책 리뷰/주니어 백엔드 개발자가 반드시 알아야 할 실무 지식

4장. 외부 연동이 문제일 때 살펴봐야 할 것들

1. 우리는 문제가 없는데

마이크로서비스 아키텍처 도입이 확대되면서 시스템 간 연동(API 호출)은 필수 요소가 되었다.

내부 서비스뿐 아니라 외부 서비스와의 연동도 증가하며 연동 지연/오류가 전체 서비스의 품질을 악화시키는 주요 원인이 되고 있다.

연동 서비스의 문제로 인한 영향을 줄이는 몇가지 방법을 살펴볼 것이다.

 

2. 타임아웃

외부 연동에서 가장 중요한 설정은 타임아웃이다.

타임아웃을 설정하지 않으면 연동 대상 시스템의 문제로 인해 전체 서비스가 지연되고 장애로 이어질 수 있다.

한 가지 예를 들어 설명하겠다.

A, B서비스가 있다.

A서비스는 요청을 200개, B서비스는 요청을 100개를 한번에 처리할 수 있을 때 A와 B를 연동해서 200개 요청을 처리 한다면  A는 문제가 없는데 B는 100개를 우선 처리하고 나머지 100개를 대기로 돌린다.

그 다음 다시 100개 요청이 들어온다면 A는 이미 요청을 처리하고 다음 요청애 응답을 B에 보내는데 B는 이미 대기중인 요청이 존재하고 다시 100개 요청을 대기로 돌리면 결국 전체적인 서비스의 지연이 생긴 것으로 보일 것이다.

여기서 이 문제를 해결할 간단한 방법으로 타임아웃을 설정하는 것이다.

타임아웃에는 연결 타임아웃, 읽기 타임아웃 2가지가 있다.

2-1. 두 가지 타임아웃: 연결 타임아웃과 읽기 타임아웃

API 요청 과정은 다음 네 단계로 진행된다.

  1. 네트워크 연결
  2. 요청 전송
  3. 응답 대기
  4. 통신 종료

연결에 너무 오래 걸리면 연결 타임아웃 으로 연결 대기 시간을 제한할 수 있다.

요청 후 응답이 지연되면 읽기 타임아웃 으로 응답 대기 시간을 제한한다.

읽기 타임아웃은 좀 더 세분화할 수 있다.

  • 소켓 타임아웃: 패킷 단위로 수신이 없을 때의 타임아웃
  • 호출 타임아웃: 요청부터 응답까지 전체 처리 시간 기준

패킷은 계속 들어오지만 전체 처리 시간이 길다면 호출 타임아웃이 먼저 종료될 수 있다.

2-2. 재시도

타임아웃과 함께 자주 사용하는 방법이 재시도다.

재시도는 연동에 실패했을 때 잠시 후다시 시도하는 것으로 연동 실패를 성공으로 바꿀 수 있다.

하지만 재시도는 언제나 해도 되는 것은 아니고 다음 세 가지 조건일 때 재시도가 안전하다.

  1. 단순 조회 기능
  2. 연결 타임아웃(연결 자체 실패)
  3. 멱등성을 가진 변경 기능

일시적인 단순 조회 오류는 재시도를 통해 성공 확률을 높일 수 있다.

연결 타임아웃 또한 연결이 준비되지 않은 상태일 수도 있기 때문에 재시도를 통해서 성공 활률을 높일 수 있다.

읽기 타임아웃은 주의가 필요한데 UPDATE/INSERT 요청의 경우 타임아웃이 걸려도 요청 자체는 실제로 성공했을 수도 있어 위험하다.

중복 처리가 일어나지 않도록 멱등성을 확보해야 하는데 멱등성이란 연산을 여러번 적용해도 결과가 달라지지 않는 성질을 말한다.

또한 재시도할 때는 다음 2가지를 결정해야 한다.

  • 재시도 횟수
  • 재시도 간격

대부분의 경우 1~2번 정도 재시도가 적당하다.

이 횟수 모두 실패하면 간헐적인 오류보다는 근본적인 문제일 가능성이 있다.

재시고 간격 또한 문제가 10초 정도 지속될 경우 재시도 간격이 짧으면 모든 재시도가 실패할수도 있다.

여러 차례 재시도 할때는 재시도 간격을 점진적으로 늘리는 것도 좋은 방법일 수 있다.

다만 짧은 간격으로 계속 재시도하면 오히려 더 큰 부하를 유발할 수 있으므로 주의해야 한다.

 

3. 동시 요청 제한

연동 API가 느려지면 우리 서비스도 함께 느려진다.

특히 특정 시점에 연동 요청이 몰릴 경우 대기열이 쌓이면서 전체 서비스 지연이 발생한다.

이를 방지하기 위해 동시 요청 수를 제한하는 벌크헤드 패턴을 적용할 수 있다.

벌크헤드 패턴은 선박의 격벽처럼 구성 요소를 분리하여 한 구역의 장애가 전체로 전파되지 못하도록 한다.

연동 서비스로 보내는 요청 수에 상한을 두어 과부하를 예방할 수 있다.

 

4. 서킷 브레이크

연동 대상 서비스에 장애가 발생했을 때, 계속 요청을 보내는 것보다 바로 실패를 반환하는 것이 오히려 더 나은 상황이 존재한다.

서킷 브레이커는 전기 차단기처럼 특정 오류 비율을 넘으면 바로 차단 모드로 진입한다.

서킷 브레이커 동작 방식

  1. 닫힘(CLOSED): 정상 상태. 모든 요청을 그대로 전달
  2. 오류 비율 계산
    • 시간 기준 비율(예: 10초 동안 50% 이상 실패)
    • 개수 기준 비율(예: 100건 중 50건 실패)
  3. 열림(OPEN): 요청을 즉시 실패로 응답해 불필요한 부하 차단
  4. 일정 시간이 지난 후 반 열림(HALF-OPEN)
    • 일부 요청만 연동 서비스로 전달해 회복 여부 점검
  5. 성공하면 닫힘, 실패하면 다시 열림

이 방식은 사용자 응답 속도를 높이고 연동 서비스의 추가 부담을 줄인다.

 

5. 외부 연동과 DB 연동

외부 연동 실패 시 보통 트랜잭션을 롤백한다.

하지만 읽기 타임아웃이 발생한 경우, 외부 서비스는 실제로 성공했을 가능성이 있다.

이때 두 시스템 간 데이터 불일치를 해결하는 두 가지 접근 방식이 있다.

5.1. 주기적 데이터 동기화

두 시스템의 데이터가 일치하는지 비교하여 보정한다.

5.2. 성공 여부 확인 API 호출

성공 상태 조회 혹은 취소 API를 호출해 처리 결과를 보정한다.

단, 이 API 호출도 타임아웃이 발생할 수 있으므로 완전한 해결책은 아니다.

5.3. DB 커넥션 풀 고갈 문제

외부 연동 로직을 트랜잭션 안에서 처리할 경우 커넥션을 반환하지 못한 채 대기 시간이 길어져 커넥션 풀이 고갈될 수 있다.

예를들어 커넥션을 가져오고 요청 처리 후에 DB처리 후 커넥션 풀을 반환할 때 연동 처리 진행중에도 커넥션을 가지고 있다.

만약 요청이 많아져서 외부 연동 처리가 느려질 경우 커넥션을 계속 가지고 있게 되는 것이다.

이런 상황에서 DB연동과 무관하게 외부 연동을 실행한다면 DB커넥션을 사용하기 전이나 후에 외부 연동을 시도 하는 방안도 고려할 수 있다.

다만 외부 연동이 트랜잭션 바깥에서 실행되기 때문에 롤백이 불가하며 실패한 외부 연동에 대한 후처리를 반드시 고민해야 한다.

 

6. HTTP 커넥션 풀

HTTP 커넥션 풀을 사용할 때 다음 세 가지를 반드시 고려해야 한다.

6.1. 풀 크기

서비스의 처리량에 따라 적절하게 설정해야 한다.

너무 크면 트래픽 집중 시 응답 지연이 가파르게 늘어난다.

6.2. 커넥션 획득 대기 시간

모든 커넥션이 사용 중일 때 대기하는 시간.

보통 1~5초 정도가 적당하다.

6.3. 커넥션 유지 시간

HTTP/1.1의 Keep-Alive 시간보다 길게 유지하면 서버에서 먼저 연결을 끊기 때문에 비효율이 발생할 수 있다.

서버 설정보다 길게 유지하지 않도록 설정해야 한다.

 

7. 연동 서비스 이중화

트래픽 증가로 연동 API 의존성이 높아졌다면 이중화를 고려할 필요가 있다.

그러나 이중화는 비용과 복잡도가 높으므로 다음을 기준으로 판단해야 한다.

  1. 해당 연동 기능이 핵심 업무인지 여부
  2. 이중화 비용이 감당 가능한 수준인지

핵심 기능이 아니라면 불필요한 비용이 될 수 있는데 예를 들어 단순 로그 기록 기능까지 이중화를 적용하는 것은 비효율적이다.