주니어 백엔드 개발자가 반드시 알아야 할 실무 지식 ‐ 외부 연동이 문제일 때 살펴봐야 할 것들 - dnwls16071/Backend_Summary GitHub Wiki

📚 외부 연동 문제⭐

  • 외부 연동에서 가장 중요한 설정 중 하나는 바로 타임아웃(Timeout)이다.
  • 연동 서비스를 호출할 때 타임아웃을 적절히 설정하지 않으면 연동 서비스에 장애가 발생했을 때 전체 서비스 품질이 급격히 나빠질 수 있다.
1. A서비스는 톰캣을 사용하고 있으며 쓰레드 풀 크기는 200이다. 즉, A서비스는 동시에 200개의 요청을 처리할 수 있다.
2. A서비스는 B서비스를 호출한다.
3. B서비스에 문제가 생겨 응답 시간이 60초를 넘기고 있다.
  • A서비스에 100개의 요청이 들어왔다고 할 때, 이 요청들은 모두 B서비스를 호출하고 있다.
  • 10초 후 A서비스에 추가로 100개의 요청이 들어왔다고 하면 B서비스를 호출하는 요청의 개수는 200개가 된다.
  • 10초 후 A서비스에 추가로 100개의 요청이 들어왔다고 하면 B서비스를 호출하는 요청의 개수는 300개가 된다.
  • 하지만 A서비스의 기존 200개 쓰레드는 아직 응답 대기 중이기 때문에 새로 들어온 100개 요청은 처리가 되지 않는다.
  • 그러나 B서비스가 응답하지 않기 때문에 대기는 끝나지 않게 되고 결국 서비스가 마비된다.

연동 서비스에 대한 타임아웃 설정⭐

  • 연동 서비스에 대한 타임아웃을 설정하지 않으면, 연동 서비스 응답이 느릴 때 처리량이 급격히 떨어진다.
  • 사용자는 응답이 올 때까지 기다리지 않는다. 새로고침과 같은 방법으로 새로운 요청을 보내게 된다.
  • 사용자 입장에서는 보낸 요청을 취소한 것이지만 개발자 입장에서는 이를 다르게 받아들여야 한다.
  • 새로고침을 하게 되면 요청을 취소하게 되는 것이 아니라 오히려 새로운 요청을 추가로 보내게 되는 것이다. 즉, 서버가 받는 부하가 배가 되는 상황이 되는 것이다.
  • 우선 네트워크 연결 시도 단계를 거친다.
  • 실제 네트워크 전송 속도는 빛보다 느리기 때문에 네트워크 상황이나 연결할 서버의 상태에 따라 연결에 오랜 시간이 걸릴 수 있다.
  • 연결에 시간이 오래 걸리면 대기 시간도 함께 증가한다. 대기 시간이 무한정 길어지게 되면 성능 문제가 발생하므로 연결 타임아웃(Connection Timeout)을 설정해 연결 대기 시간을 제한해야 한다.
  • 그 다음으로 연결이 되면 요청을 전송하고 응답을 기다리게 된다.
  • 이 때, 응답을 받기까지 시간이 오래 걸리면 대기 시간 문제가 다시 발생하게 된다. 따라서 읽기 타임아웃(Read Timeout)을 설정해서 응답 대기 시간을 설정해야 한다.
  • 대부분은 아래와 같은 타임아웃 시간을 설정하고 추이를 보며 조정한다.
    • 연결 타임아웃(Connection Timeout) : 3~5s
    • 읽기 타임아웃(Read Timeout) : 5~30s
  • 읽기 타임아웃이 연결 타임아웃에 비하면 다소 길 수 있으나 너무 짧게 설정하게 되면 연동 서비스가 정상적으로 처리했음에도 불구하고 타임아웃 에러가 발생할 수 있다.

📚 타임아웃과 재시도⭐

  • 재시도를 통해 연동 실패를 줄일 수 있으나 항상 재시도를 할 수 있다는 것은 아니다. 다음과 같은 경우에 대해서만 재시도를 해도 된다.
    • 단순 조회 기능
    • 연결 타임아웃
    • 멱등성(Idempotent)을 가진 변경 기능

재시도 횟수와 간격⭐

  • 재시도 횟수를 결정한다. 대부분의 경우 1~2번 정도의 재시도가 적당하다.
  • 재시도 간격 역시 중요하다. 여러 차례 재시도할 때는 재시도 간격을 점진적으로 늘리기도 한다. 이를 통해 연동 서버에 가해지는 부하를 일부 완화할 수 있다.

재시도 폭풍 안티패턴

  • 재시도를 통해 성공 확률을 높일 수 있지만 반대로 말하자면 연동 서비스에는 더 큰 부하를 줄 수 있다.
  • 재시도를 검토할 때는 연동 서비스의 성능 상황도 반드시 고려해야 한다.

📚 동시 요청 제한과 서킷 브레이커

동시 요청 제한

  • 순간적으로 트래픽이 몰려 발생할 수 있는 문제에 대해서 503(Service Unavailable) HTTP 상태 코드를 사용하면 과부하 상황임을 클라이언트에 알려 알맞은 오류 메시지를 출력할 수 있다.

서킷 브레이커(Circuit Breaker)

  • 연동 서비스에 과부하가 발생해 응답을 제대로 주지 못하고 있는 경우 요청을 보내면 에러가 발생하게 된다.
  • 서킷 브레이커는 위와 같이 닫힘(Closed), 열림(Open), 반 열림(Half-Open)의 3가지 상태를 가진다.
  • 서킷 브레이커는 닫힘 상태로 시작한다. 닫힘 상태일 때는 모든 요청을 연동 서비스에 전달한다.
  • 외부 연동 과정에서 오류가 발생하기 시작하면 지정한 임계치를 초과했는가를 확인한다. 만약 실패 건수가 임계치를 초과하면 서킷 브레이커는 열림 상태가 된다.

❗보통 임계치는 다음 조건 중 하나를 사용한다.

  • 시간 기준 오류 발생 비율 - 예를 들어 10초 동안 오류 비율이 50%를 초과했는가?
  • 개수 기준 오류 발생 비율 - 예를 들어 100개의 요청 중 오류 비율이 50%를 초과했는가?
  • 열림 상태가 되면 연동 요청을 수행하지 않고 바로 에러 응답을 리턴한다.
  • 열림 상태는 지정된 시간 동안 유지된다. 이 시간이 지나면 반 열림 상태로 전환된다.
  • 반 열림 상태가 되면 일부 요청에 한해 연동을 시도하게 된다.
  • 일정 개수 또는 일정 시간 동안 반 열림 상태를 유지하게 되며 이 기간동안 연동에 성공하면 닫힘 상태로 복귀한다.
  • 반대로 연동에 실패하면 다시 열림 상태로 전환되며 연동을 하지 않게 된다.
  • 서킷 브레이커가 열림 상태로 있게 되면 연동 서비스에 요청이 전달되지 않기 때문에 연동 서비스가 과부하로부터 벗어날 수 있는 기회가 될 수 있다.

📚 DB와 외부 연동⭐

외부 연동과 트랜잭션 처리 1 - 외부 연동에 실패했을 때 트랜잭션 롤백

  • 만약 트랜잭션 범위 안에서 외부 연동에 실패한 경우 트랜잭션을 롤백할 수 있다.
  • 해당 경우에는 외부 연동에 실패했을 때 트랜잭션을 롤백하면 변경된 데이터가 DB에 반영되지 않으므로 단순하게 생각이 가능하다.

외부 연동과 트랜잭션 처리 2 - 읽기 타임아웃 에러가 발생했지만 실제로 외부 연동 서비스에서의 로직은 정상적으로 호출된 경우

  • 정확히 말하면 외부 서비스에 연동은 했으나 READ_TIMEOUT(읽기 타임아웃)이 발생해 트랜잭션을 롤백해 외부 서비스가 실제로 성공했을 경우이다.
    • 일정 주기로 두 시스템의 데이터가 일치하는지 확인하고 보정하는 방법이다.
    • 성공 확인 API 호출(단 이 방식은 외부 서비스에서 제공이 되어야만 가능)
    • 취소 확인 API 호출(단 이 방식은 외부 서비스에서 제공이 되더야만 가능)

외부 연동과 트랜잭션 처리 3 - 외부 연동은 성공했는데 DB 연동은 실패해서 트랜잭션을 롤백하는 경우

  • 외부 연동은 성공했으나 DB 연동에 실패해서 트랜잭션을 롤백한 경우에는 취소 API를 호출해 외부 연동을 이전 상태로 되돌리는 것이 필요하다.
  • DB 연동에 실패했기 때문에 이 경우 성공 확인 API를 호출해봐야 의미가 없다. 취소 API가 없거나 취소에 실패할 수도 있기에 데이터 일관성이 중요한 서비스라면 일정 주기로 데이터가 맞는지 비교하는 프로세스를 갖추는 것이 좋다.

📚 HTTP 커넥션 풀⭐

  • DB 커넥션 풀이 DB 연결에 걸리는 시간을 줄여 성능을 높이는 것처럼 HTTP 연결도 커넥션 풀을 사용하면 연결 시간을 줄일 수 있어 응답 속도 향상에 도움이 된다.
  • HTTP 커넥션 풀을 사용할 때는 다음 3가지를 고려해야 한다.
    • HTTP 커넥션 풀의 크기
    • 풀에서 HTTP 커넥션을 가져올 때까지 대기하는 시간
    • HTTP 커넥션을 유지하는 시간(Keep-Alive)
  • 풀의 크기는 연동할 서비스 성능에 따라 결정해야 한다. 무턱대고 커넥션 풀 크기를 늘리면 순간적으로 트래픽이 몰릴 때 연동 서비스 응답 시간이 급격히 느려진다.
  • 대기 시간이 길어지면 전체 응답 시간도 같이 늘어나기에 대기 시간은 수 초 이내의 짧은 시간이 적당하다.
  • 커넥션은 무한히 유지되지 않는다. 연동 서비스가 일정 시간 범위동안만 커넥션을 유지한 뒤 연결을 끊는 경우도 있다.
  • 끊어진 커넥션을 사용하게 되면 에러가 발생하기 때문에 연동 서비스에 맞춰 유지 시간을 적절히 설정해야 한다.
  • 예를 들어, HTTP/1.1에서는 서버가 Keep-Alive 헤더로 연결 유지 시간을 지정하는데 이 시간이 지나면 서버는 연결을 끊는다.