서비스 자체는 문제가 없는데, 외부 연동 서비스의 트래픽 처리 한계로 인해 장애가 발생하는 경우가 많습니다.
- A 서비스는 외부 인증 정보를 얻기 위해 B 서비스에 연동 요청을 보냅니다.
- 하지만 B 서비스가 몰려드는 트래픽을 감당하지 못하고, 응답을 제대로 주지 못합니다. (에러발생!!!)
이처럼 연동 서비스의 문제를 완전히 차단하기는 어렵습니다.
왜냐하면, 외부 연동이 필수적인 경우가 많기 때문이죠.
외부 연동이 모든 상황에서 완벽할 수는 없기에, 이에 대비한 전략이 필요합니다.
이 글에서는 외부 연동에서 발생할 수 있는 문제 상황과 이를 해결하는 방법을
"주니어 개발자가 반드시 알아야할 실무지식" 해당 책을 토대로 정리 해보았습니다.
타임아웃, 설정하셨나요?
외부 연동에서 가장 중요한 설정 중 하나는 바로 타임아웃입니다.
설정을 하지 않으면, 외부 연동이 느릴 때 응답 대기로 인해 서버 자체가 마비될 수 있습니다.
타임아웃 문제 예시
A 서비스는 톰캣을 사용하며, 스레드 풀 크기는 200입니다.
A가 B서비스에 요청을 100개 보냈는데, B가 성능 악화로 응답 시간이 60초 이상 걸리기 시작했습니다.
- A 서비스의 100개 요청은 전부 대기 상태가 됩니다.
- 그 와중에 또 다른 100개 요청이 오면, 모든 스레드가 대기로 잠식되고 서비스가 점점 마비됩니다. 😱
- A 서비스는 B의 응답 여부와 상관없이 무한 대기 중일 테니까요.
결과적으로, 외부 연동 서비스의 문제 때문에 A 서비스마저 영향을 받게 되는 상황이죠.
해결책: 타임아웃 설정하기!
타임아웃은 연동 서비스의 응답 대기 시간을 제한해 이런 문제를 방지합니다.
특히, 아래 두 가지 타임아웃 설정을 꼭 확인해 주세요.
- 연결 타임아웃: 네트워크 연결 시도 단계에서 대기 시간을 제한
- 읽기 타임아웃: 요청을 전송한 뒤, 응답 대기 시간을 제한
추천 초기값
처음 연동 서비스에 타임아웃을 설정할 때는 다음과 같은 값을 추천드립니다:
- 연결 타임아웃: 3초 ~ 5초
- 읽기 타임아웃: 5초 ~ 30초
읽기 타임아웃이 길게 느껴질 수 있지만, 너무 짧으면 아직 정상 작동 중인 요청도 에러로 처리될 수 있으니 신중히 설정해야 합니다.
실패했나요? 그러면 재시도!
연동이 실패했을 때, 간단한 해결 방법 중 하나는 재시도입니다.
네트워크 통신 과정에서 간헐적인 연결 실패나 응답 지연은 흔히 발생하니까요.
“모든 요청에서 재시도가 가능할까요?”
그렇지 않습니다. 요청 데이터를 신중히 고려해야 합니다.
잘못된 재시도가 데이터를 중복 저장하거나, 심지어 서비스 오류를 유발할 수 있으니까요. 😅
따라서 다음 조건에 해당하는 경우에만 재시도를 적용하세요.
- 단순 조회 기능: 여러 번 호출해도 서비스에 영향을 미치지 않습니다.
- 연결 타임아웃 발생: 요청이 아예 연동 서비스까지 도달하지 못했을 경우입니다.
- 멱등성 가진 변경 기능: 멱등성을 가진 API라면, 여러 번 호출해도 최종 결과가 같습니다.
멱등성(idempotency)이란? 같은 요청을 여러 번 보내도 항상 같은 결과가 나오는 것을 의미합니다. 예를 들어, 콘텐츠에 "좋아요 추가"를 누르면 이미 좋아요를 한 상태라면 아무 작업도 하지 않고, 200 상태 코드만 반환합니다.
반면, 비멱등성 요청(예: 포인트 차감 API)의 경우는 조심해야 합니다.
재시도를 잘못하면 포인트가 두 번 차감될 수도 있으니까요.
추천 초기값
- 재시도 횟수: 보통 1~2번 정도가 적당합니다. 그 이후에도 실패하면, 원인이 간헐적 오류가 아니라 근본적인 문제일 가능성이 높습니다.
- 재시도 간격: 간격은 점진적으로 늘려가면서 서버 부하를 줄일 수 있습니다.
- 예: 1초 → 2초 → 3초 간격으로 재시도
동시 요청 제한
연동 서비스가 동시에 처리할 수 있는 요청 수에 한계가 있다면, 요청 자체를 제한할 필요가 있습니다.
예를 들어 연동 서비스는 한 번에 100개 요청만 처리할 수 있는데, 클라이언트가 300개의 요청을 보냈다고 가정해 봅시다.
- 연동 서비스에 무리하게 300개 요청을 모두 전달하면, 처리 속도가 급격히 느려지거나 서비스 자체가 죽을 가능성이 있습니다.
- 결과적으로 자체 서비스인 우리 시스템도 연동 서비스 장애의 영향을 받아 문제가 발생합니다.
해결 법: 동시 요청 제한
이런 문제를 막기 위해, 300개 요청 중 100개 요청만 처리하고 나머지는 거부하도록 설정할 수 있습니다.
- 과도한 요청 차단: 차단된 요청은 503 HTTP 상태 코드(Service Unavailable)로 응답하여 클라이언트에 트래픽 초과 상황임을 알립니다.
- 서버 안정 유지: 연동 서비스의 최대 요청 처리량을 초과하지 않도록 제한함으로써 서비스 장애를 예방할 수 있습니다.
이는 연동 서비스와 자체 서비스의 안정성을 함께 유지해 주는 간단하면서도 효과적인 방법입니다.
실제 사례: 토스뱅크의 강연 자료에 따르면, 신용정보 외부서비스에 확인 요청을 보낼 때, 동시 요청 제한하여 외부 서비스가 과도한 트래픽으로 서버가 터지지 않도록 노력한다고 합니다.
서킷 브레이커를 활용해 보세요
연동 서비스에 과부하가 발생한 상황에서 서킷 브레이커는 아주 강력한 도구입니다.
서킷 브레이커는 장애 상황에서 요청을 차단해, 문제를 악화시키지 않도록 도와줍니다.
서킷 브레이커의 상태
- 닫힘(closed): 모든 요청이 정상적으로 연동 서비스로 전달됩니다.
- 열림(open): 연동 오류가 임계치를 넘어섰을 때, 외부로의 모든 요청을 차단합니다.
- 반열림(half-open): 일정 시간이 지난 뒤, 정상화 여부를 확인하기 위해 일부 요청만 전달해 봅니다.
임계치 예시:
- 오류 비율이 10초 동안 50% 이상 발생
- 100개 중 50개 이상의 요청에서 에러 발생
열림 상태 이후, 일정 시간이 지나고 반열림 상태에서 성공 여부를 확인합니다. 성공하면 닫힘 상태로 돌아가고, 실패하면 다시 열림 상태가 됩니다. 이 방식은 연동 서비스가 과부하에서 좀 더 빠르게 복구할 수 있도록 돕습니다.
외부 연동과 DB 연동
서비스를 개발하다 보면 외부 서비스와의 연동이 생길 수밖에 없죠. 예를 들어 회원가입 요청 시 외부 API를 호출해 회원 정보를 검증하거나 저장하는 작업을 생각해 보세요.모든 것이 잘 돌아간다면 DB에 회원 정보가 저장되고, 외부 시스템에도 해당 데이터가 정상적으로 반영될 겁니다.
그런데, 항상 모든 것이 완벽히 돌아가진 않잖아요?
- DB 저장 과정에서 실패할 수도 있고,
- 외부 서비스 호출 중 예상치 못한 네트워크 에러가 발생할 수도 있습니다.
외부 연동과 트랜잭션 처리
아래는 자주 마주치는 두 가지 케이스입니다.
외부 연동에 실패했을 때 트랜잭션을 롤백
A 서비스를 기준으로 외부 연동이 실패하면, DB에 저장된 정보도 의미 없겠죠? 그래서 트랜잭션을 롤백하게 됩니다.
하지만 읽기 타임아웃이 발생해 요청이 실패로 처리되더라도, 실제로 외부 서비스에서는 요청 처리가 성공했을 수도 있어요.
이럴 때 문제가 생기는데요, 이 경우 두 가지 해결책을 고민할 수 있습니다.
- 데이터 일치 보정 프로세스 : 일정 주기로 두 시스템의 데이터를 비교하고, 불일치한 데이터를 보정하는 방법입니다.예를 들어, 주문 서비스와 포인트 서비스를 매일 밤 한 번씩 조회해서 전날의 포인트 사용 내역이 일치하는지 확인합니다.
- 성공 확인 API 호출: 외부 작업 처리 결과를 확인하는 API를 호출할 수 있다면, 성공 여부에 따라 후속 조치를 결정합니다.
- 성공 응답: 트랜잭션을 그대로 커밋
- 실패 응답: 롤백
- 취소 API 지원: 취소 API를 호출해 외부 시스템 상태를 초기화하는 방법도 고려해 볼 수 있습니다.
이 반대 상황도 생길 수 있죠.
외부 연동은 성공했지만 DB 연동이 실패한 경우
외부 서비스 연동은 성공했지만, A 서비스의 DB 저장이 실패하여 트랜잭션이 롤백되었다고 가정해 봅시다. 이 경우에는 다음과 같은 대처가 필요합니다.
- 취소 API 호출 : 외부 서비스가 제공하는 취소 API를 호출하여 연동 작업에 대한 처리를 되돌립니다.
- 데이터 검증 프로세스 구축 : 그러나 취소 API가 없거나 실패할 가능성도 있으니, 데이터 일관성이 중요한 서비스라면 주기적인 데이터 검증 프로세스를 구축해 불일치 데이터를 정리하는 것이 안전합니다.
외부 연동이 느려질 때, DB 커넥션 풀 문제
외부 연동이 느리면 발생할 수 있는 또 하나의 문제는 DB 커넥션 풀 부족 현상입니다.
다음 프로세스를 살펴볼게요:
- DB 커넥션 풀에서 커넥션을 가져옵니다.
- 필요하다면 간단한 DB 쿼리를 실행합니다. (0.1초 소요)
- 외부 연동 API를 호출합니다. (4.8초 소요)
- 마지막으로 DB 쿼리를 한 번 더 실행한 뒤 커넥션을 반환합니다.
여기서 핵심은 DB 커넥션 사용 시간입니다.
실제로 DB를 사용한 시간은 0.2초밖에 되지 않아요.
하지만 외부 연동 처리(4.8초)로 인해 DB 커넥션이 약 5초 동안 점유됩니다.
이런 문제를 해결하려면?
- 외부 연동 작업을 DB 트랜잭션 외부로 이동시켜 DB 커넥션 점유 시간을 줄이는 방안을 고려할 수 있습니다.
- 예를 들어, 외부 호출을 비동기 이벤트로 처리한 뒤 DB와의 연결을 바로 종료하는 방법도 있습니다.다만, 이 방법을 사용할 경우 외부 연동 실패 시의 데이터 보정 로직이 반드시 필요합니다.
- 보상 트랜잭션(Compensating Transaction): 이미 커밋된 데이터를 되돌릴 추가 트랜잭션을 설계
- 후보정 프로세스: 기능 특성에 따라 사후 데이터 정정
HTTP 커넥션 풀
DB에 커넥션 풀이 있다면 HTTP 요청도 마찬가지입니다.
HTTP 연결 시에도 커넥션 풀이 있으면, 매번 새로 연결을 시도하지 않아도 돼서 성능이 좋아집니다.
여기서 이야기하는 HTTP 커넥션 풀은 클라이언트와의 HTTP 커넥션이 아닙니다.
HTTP 커넥션 풀은 보통 서버가 다른 서버로 요청을 보내기 위한 연결을 효율적으로 관리하기 위해 사용됩니다. 이때 커넥션 풀을 사용하는 주체는 서버 내부에서 사용하는 HTTP라이브러리(HttpClient, RestTemplate, WebClient 등)이며, 브라우저(클라이언트)가 서버에 요청을 보내는 것과는 구분됩니다.
클라이언트(브라우저 등)와는 어떻게 다른가?
클라이언트(브라우저 등)가 서버에 요청을 보낼 때는, 브라우저가 자체적으로 HTTP 연결을 관리합니다.
- 브라우저는 요청 시 서버로 TCP 연결을 맺고, 요청을 처리한 뒤 연결을 닫거나, 일정 시간 동안 재사용(Keep-Alive)합니다.
- 이 과정은 HTTP 커넥션 풀과는 별개로 브라우저 내부에서 자동으로 처리됩니다.
다시 본론으로 돌아와 외부 연동과 연결되는 HTTP 커넥션 풀이 있다면 다음 3가지를 고려해야 합니다:
- 커넥션 풀 최대 크기: 풀의 크기가 너무 작으면 대기가 길어질 수 있고, 너무 크면 리소스 낭비가 심해요.
- 풀에서 커넥션을 가져올 때까지의 대기 시간: 적절한 타임아웃 값을 설정해 주세요.
- 커넥션 유지 시간(TTL): 커넥션을 필요 이상으로 오래 유지하면 메모리 점유율이 높아질 수 있어요.
연동 서비스 이중화
혹시 중요한 연동 서비스가 한 대의 서버에만 의존하고 있다면 한번 “이중화”를 고려해 봐야 할 시점일지도 모릅니다.
결제 서비스는 서비스 유지에 핵심 기능입니다.
만약 이 연동이 실패하면 매출이 0원이 되는 상황이 발생할 수 있어요.
그런데 결제 서비스를 A/B 두 서버로 이중화한다면 어떨까요? A가 장애가 나도 B를 통해 결제가 가능해집니다.
이중화 도입 시 고려할 사항
- 해당 기능이 서비스의 핵심 기능인가요?
- 이중화로 인한 비용이 감당 가능한 수준인가요?
'CS' 카테고리의 다른 글
동시성 문제 어떻게 해결해야하지? (2) | 2025.06.21 |
---|---|
비동기 연동 언제 어떻게 써야할까? (2) | 2025.06.10 |
성능을 좌우하는 DB 설계와 쿼리 최적화 (2) (2) | 2025.05.26 |
성능을 좌우하는 DB 설계와 쿼리 최적화 (1) (1) | 2025.05.25 |
느려진 서비스 어디부터 봐야할까? (2) (0) | 2025.05.20 |