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

JUN·2025년 12월 1일

외부 연동 문제 설정법

1. 타임아웃 설정을 하자

다음과 같은 상황이 있다고 생각해보자.

  • A 서비스가 있고 이 서비스가 호출하는 B 서비스가 있다.
  • A 서비스의 스레드 풀은 100개이다.
  • B 서비스에 성능 문제가 생겨 응답 대기 시간이 60초를 넘기기 시작한다.

A 서비스에 100명의 사용자가 요청을 보내면 톰캣은 A 서비스의 요청을 처리하기 위해 B서비스를 호출한다.

10초 후 새로운 100명의 사용자가 요청을 보낸다. 하지만 B 서비스의 요청에 응답이 오지 않았기 때문에 대기는 끝나지 않는다. 이렇게 서비스가 마비된다.

→ 연동 서비스에 대한 타임아웃을 설정하지 않으면 연동 서비스의 응답이 느려질 때에 처리량이 급격히 떨어진다.

타임아웃을 설정하여 사용자에게 지정 시간 이후 에러 화면을 보게 하자. 반응 없는 무한 대기 화면보다는 에러 화면이라돋 보는 게 낫다. 또한 서비스의 자원이 포화되기 전에 응답하게 되므로 연동 서비스 문제가 다른 기능에 주는 영향도 줄어든다.

타임아웃의 종류

image.png

  1. 연결 타임아웃(Connection Timeout)

    커넥션을 요청할때에 커넥션 수락을 얼마나 기다릴지 기다리는 시간

  2. 읽기 타임아웃(Read Timeout)

    커넥션이 되고 응답을 받기까지의 시간

처음 연동하는 서비스라면 보통 이렇게 타임아웃을 설정해둔다.

  • 연결 타임아웃 : 3초 ~ 5초
  • 읽기 타임아웃 : 5초 ~ 30초

소켓 타임아웃과 읽기 타임아웃

  • 읽기 타임아웃을 지정할 때에는 실제로 설정되는 값이 무엇인지 보고 결정해야함.
  1. 아파치의 HTTPClient 의 경우 소켓 타임아웃을 설정함
    1. 소켓 타임아웃은 네트워크 패킷 단위를 기준으로 하므로 전체 응답 시간에 대한 타임아웃을 의미지는 않는다.
  2. OkHttp 는 읽기 타임아웃과는 별개로 호출 타임아웃을 설정 가능
    1. 요청 시작부터 응답까지의 전체 시간 기준으로 설정
  3. 소켓 타임아웃을 설정하면 패킷은 계속 수신되지만 전체 처리 시간이 오래 걸리는 경우 타임아웃 발생시킬 수 잇음.

OkHttp란?

OkHttp는 HTTP 및 HTTP/2 클라이언트 라이브러리로, 안드로이드 및 자바 애플리케이션에서 네트워크 요청을 처리하는 데 널리 사용되는 오픈소스 라이브러리입니다. Square사에서 개발했으며, 효율적인 연결 관리와 다양한 타임아웃 설정 옵션을 제공합니다.

"소켓 타임아웃은 네트워크 패킷 단위를 기준으로 하므로 전체 응답 시간에 대한 타임아웃을 의미하지는 않는다"의 의미

이 문장은 소켓 타임아웃의 작동 방식을 설명하는 중요한 개념입니다:

  • 소켓 타임아웃의 특성: 소켓 타임아웃은 각 네트워크 패킷이 도착하는 간격을 측정합니다. 즉, 패킷과 패킷 사이의 대기 시간을 기준으로 합니다.
  • 전체 응답 시간과의 차이: 만약 서버가 데이터를 천천히 보내더라도, 패킷이 계속해서 도착하기만 한다면 타임아웃이 발생하지 않습니다. 예를 들어, 소켓 타임아웃을 10초로 설정했다고 가정해봅시다. 서버가 총 5분 동안 응답을 보내는데, 매 5초마다 패킷을 보낸다면 타임아웃이 발생하지 않습니다. 전체 응답 시간은 5분이지만, 패킷 간 간격은 10초 미만이기 때문입니다.
  • 문제점: 이로 인해 전체 처리 시간이 매우 길어질 수 있습니다. 클라이언트는 서버로부터 느리게 전송되는 데이터를 계속 기다리게 되어, 의도하지 않게 매우 긴 시간 동안 연결이 유지될 수 있습니다.

OkHttp의 해결책

OkHttp는 이러한 문제를 해결하기 위해 읽기 타임아웃과는 별개로 "호출 타임아웃(Call Timeout)"을 제공합니다. 호출 타임아웃은 요청 시작부터 응답 완료까지의 전체 시간을 기준으로 작동하므로, 서버가 아무리 패킷을 계속 보내더라도 전체 처리 시간이 설정된 시간을 초과하면 타임아웃이 발생합니다.

2. 외부 설정 실패 시 재시도 설정법

재시도 시 문제가 없는 서비스만 재시도를 할 것.

  1. 재시도를 할 때에는 재시도 해도 문제 없는 서비스인지 확인해야한다.
  2. 예를 들어 API 를 호출해 포인트가 차감되는 서비스의 경우 포인트 차감 처리가 여러번 될 수 있다.
  3. 재시도 해도 되는 조건은 다음과 같이 3가지가 있다.
    1. 단순 조회 기능
    2. 연결 타임 아웃 (연결 타임 아웃의 경우 아직 연동 서비스에 연결되지 않았다는 의미이므로 재시도 괜찮다. 하지만 읽기 타임아웃의 경우에는 재시도 시 문제가 발생할 수 있다.)
    3. 멱등성을 가진 변경 기능 ( 상태 변경되는 API 를 재시도할 때에는 멱등성을 고려해야한다. )

재시도 횟수와 간격을 생각할 것.

  1. 재시도 할 때에는 재시도 횟수와 간격을 생각해야한다.
  2. (대부분의 경우) 재시도의 횟수는 1~2회가 이상적이다.
  3. 재시도 간격의 경우에는 여러 차례 재시도 할 경우 간격을 늘리면서 한다.

재시도 폭풍(Retry Storm) 의 안티패턴

  1. 재시도는 성공 가능성을 높일 수 있지만 반대로 연동 서비스에는 더 큰 부하를 줄 수 있다.

3. 동시 요청 제한

  1. 연동 서비스의 최대 처리량을 초과하는 트래픽을 감당하면 기하급수적으로 응답시간이 늘어나기 시작한다.
  2. 이러한 최대 처리량을 늘릴 수 없을 때에 연동 서비스에 임계치 이상의 요청을 보내는 방법은 최대 처리량을 초과하는 요청을 보내지 않는 것이다.
  3. 연동 서비스의 트래픽을 초과하는 요청은 503 상태 코드를 사용하여 과부하 상황임을 클라이언트에 알려 오류 메시지를 출력할 수 있다.

벌크 헤드 패턴

동시요청을 제한하는 방식은 벌크페드 패턴이라고 한다. 벌크헤드 패턴은 각 구성요소를 격리하여 한 구성요소의 장애가 다른 구성요소에 영향을 주지 않도록 ㅅ하는 설계 패턴이다.

4. 서킷 브레이커

  1. 앞서 연동 서비스에 시간이 걸릴 경우에는 타임아웃을 설정하여 지정 시간 이상 걸릴 경우에는 에러 화면이 보여지는 방법을 설명했다.

  2. 하지만 연동 서비스가 정상 상태가 아님을 알 수 있다면 연동 서비스에 요청을 보내지 않고 바로 에러를 응답하는 것이 낫다.

  3. (왜냐하면) 서버 입장에서는 읽기 타임아웃 시간동안 발생하는 응답시간을 줄일 수 있고 처리량도 감소시킬 수 있고 사용자 입장에서는 몇초 기다리는 것 보다 바로 에러 화면을 보는 것 보다 바로 에러화면을 보는 것이 낫기 때문이다.

  4. 서킷 브레이커가 작동하는 방식이 이와 같다.

  5. 서킷 브레이커는 누전 차단기처럼 과도한 트래픽이 발생하면 연동을 중지시키고 바로 에러를 응답한다.

  6. 서킷 브레이커는 닫힘, 열림, 반 열림 의 3가지 상태를 갖는다.

    image.png

    1. 서킷 브레이커는 닫힘 상태로 시작한다. 닫힘 상태일 때에는 모든 요청을 연동 서비스에 전달한다.
    2. 외부 연동 과정에서 오류가 발생하기 시작하면 지정한 임계치를 초과했는지 확인하고 초과했다면 열림 상태가 된다.
      1. 임계치 기준
        • 시간 기준 오류 발생 비율 : 10초동안 오류 비율이 50% 초과
        • 개수 기준 오류 발생 비율 : 100개 요청 중 오류 비율이 50% 초과
    3. 열림 상태가 되면 연동 요청은 수행하지 않고 바로 에러 응답을 리턴한다. 이는 지정된 시간동안 진행된다.
    4. 이 시간이 지나면 일정 개수, 일정 기간 동안 반 열림 상태로 전환된다. 일부 요청에 관해 연동을 시도한다.
    5. 이 기간 연동에 성공하면 다시 열림 상태로 전환되어 연동을 차단한다.

5. 외부 연동과 DB 연동

외부 연동과 트랜잭션 처리

  1. DB 연동과 외부 연동을 함께 처리할 때에는 오류 발생 시 DB 트랜잭션을 어떻게 처리할지 알맞게 판단해야한다.
    1. 예시 1. 외부 연동에 실패했을 때 트랜잭션을 롤백

      1. 이 상황에서는 외부 연동에 실패했을 때 트랜잭션이 롤백되면 변경 데이터가 DB 에 저장되지 않는다.
      2. 하지만 읽기 타임아웃이 발생했을 경우에는 외부 서비스가 실제적으로 성공적으로 처리되었을 가능성을 염두해 둬야 한다.
      3. 이 경우 2가지 방법 중 하나를 검토해야한다.
      4. 첫 번째로 일정 주기로 두 시스템의 데이터가 일치하는 지 확인하고 보정하는 방법
      5. 성공 확인 API 를 호출하는 방법 ( 이 방식은 외부 서비스가 성공 여부를 알려주는 API 를 제공할 때만 사용할 수 있다.
      6. 이 방식의 형으로 취소 API 를 호출하는 방식도 있다. 읽기 타임아웃이 발생한 뒤 일정 시간 후에 취소 API 를 호출하는 식이다.
      7. 하지만 이것은 연동 서비스가 지원해야 하고 후속 API 호출에도 읽기 타임아웃이 발생할 수 있으므로 두 시스템간 일관성이 중요하다면 정기적으로 데이터 일치를 확인하는 프로세스를 갖추는 것이 바람직하다.
    2. 예시 2. 외부 연동은 성공했지만 DB 연동에 실패해 트랜잭션을 롤백

      외부 연동은 성공했지만 DB 연동에 실패된 경우에는 취소 API를 호출해서 외부 연동을 이전 상태로 되돌리는 것이 필요하다.

외부 연동이 느려질 때 DB 커넥션 풀 문제

  1. DB 트랜잭션 범위 안에서 외부 연동을 수행할 때에 트랜잭션 처리 외에도 주의해야할 점이 하나 더 있다.
  2. 바로 커넥션 풀 부족 현상이다.
  3. DB 연동과 무관하게 외부 연동을 실행할 수 있다면 DB 커넥션을 사용하기 전에나 후에 외부 연동을 시도하는 방안을 고려해보면 좋다.
  4. 단 이 방식은 외부 연동이 트랜잭션 범위 밖에서 실행되기 때문에 롤백이 불가능ㅎ아다. 실패한 외부 연동에 대한 후처리가 이루어져야함을 주의하자

6. HTTP 커넥션 풀

  1. 전체 처리 시간에서 연결 시간이 차지하는 비중이 크다.
  2. DB 도 DB 커넥션 풀을 통해 dB 연결 시간을 줄이는 것 처럼 HTTP 연결도 커넥션 풀을 사용하면 연결 시간을 줄일 수 있어 응답속도 향상에 도움이 된다.
  3. HTTP 커넥션 풀을 사용할 때는 다음 3가지 를 고려하자
    1. HTTP 커넥션 풀 크기

      풀의 크기는 연동할 서비스의 성능에 따라 결정한다. 커넥션풀 크기를 무작정 늘리면 연동 서비스의 성능 저하가 우리 서비스 전체의 응답시간에 영향을 끼친다. 그렇기에 반드시 연동 서비스의 처리 능력을 고려하자

    2. 풀에서 HTTP 커넥션을 가져올 때까지 대기하는 시간

      커넥션 풀의 크기가 10개라면 11개의 외부 연동요청이 들어온 경우 10개는 커넥션을 확보해 실행되고 나머지 1개는 대기하게 된다. 대기 시간은 수 초 이내의 짧은 시간으로 설정하는 것이 좋다.

    3. HTTP 커넥션 유지 시간

      끊어진 커넥션을 사용하면 에러가 발생하ㅡ로 연동 서비스에 맞춰 유지 시간을 적절히 설정하자.

7. 연동 서비스 이중화

  1. 서비스가 대량 트래픽을 처리할 만큼 성장했담녀 연동 서비스의 이중화를 고려하자.
  2. 연동 서비스를 이중화해두면 장애 시 대응할 수 잇다.
  3. 하지만 비용이 크기에 결정시 다음 2가지를 따져봐야한다.
    1. 해당 기능이 서비스의 핵심 기능인지
    2. 이중화 비용이 감당 가능한 수준인지
profile
순간은 기록하고 반복은 단순화하자 🚀

0개의 댓글