최범균 - 주니어 백엔드 개발자가 반드시 알아야 할 실무 지식
외부 연동이 문제일 때 살펴봐야 할 것들
우리는 문제가 없는데
- 연동하는 서비스에 장애가 발생하면 우리 서비스도 영향을 받는다.
- 연동 서비스가 필수인 경우도 있기에, 완벽히 문제를 차단하긴 어렵지만 영향도를 줄일 수 있는 방법은 있다.
타임아웃
- 반응 없는 무한 대기보다는 에러 화면이라도 보는 것이 더 낫다.
- 서버는 사용자 요청에 대해 (스레드 풀 같은) 자원이 포화되기 전에 응답하게 되므로, 연동 서비스 문제가 다른 기능에 주는 영향을 줄일 수 있다.
연결/읽기 타임아웃
- 연결 타임아웃: 네트워크 연결 시도 단계
- 읽기 타임아웃: 응답을 받기까지 시간
- 처음 연동하는 서비스라면 연결 타임아웃은 3~5초, 읽기 타임아웃은 5~30초로 설정하는 게 좋다.
- 읽기 타임아웃은 내 서버에서는 타임아웃 되어 정상처리가 되지 않으나, 연동 서버에서는 정상 처리 될 수 있음
- 결제처럼 민감한 기능은 읽기 타임아웃 시간을 약간 길게 설정해서 간헐적으로 연동 시간이 길어지더라도 정상적으로 처리할 수 있어야 한다.
재시도
재시도 가능 조건
- 재시도를 통해 연동 실패를 줄일 수 있지만, 항상 재시도를 할 수 있는 것은 아니다. 연동 API를 다시 호출해도 되는 조건인지 확인해야 한다.
재시도를 해도 되는 조건은 다음 3가지로 정리 가능하다.
1. 단순 조회 기능
2. 연결 타임아웃
3. 멱등성을 가진 변경 기능
재시도 횟수와 간격
- 지수 백오프 전략(재시도 간격을 점진적으로 늘림)으로 재시도한다.
- 많은 재시도는 서버에 부하를 줄 수 있다.
동시 요청 제한
벌크헤드
- 연동 서비스에 특정 횟 수 이상 요청을 하지 않도록, 그 이상 요청이 오면 거절한다.
- 벌크헤드 패턴은 각 구성 요소를 격리함으로써 한 구성 요소의 장애가 다른 구성요소에 영향을 주지 않도록 하는 설계 패턴이다.
서킷 브레이커
- 전기 회로의 차단기처럼, 일정 횟수 이상 연속적으로 오류가 발생하면 회로를 “열어(Open)” 더 이상 해당 서비스로 요청을 보내지 않고, 일정 시간이 지난 뒤 일부 요청만 허용하는 “하프 오픈(Half-Open)” 상태를 거쳐, 서비스가 정상화되면 다시 “닫힘(Closed)” 상태로 돌아갑니다.
임계치는 다음 조건 중 하나를 선택할 수 있다.
- 시간 기준 오류 발생 비율: 예) 10초 동안 오류 비율이 50% 초과
- 개수 기준 오류 발생 비율: 예) 100개 요청 중 요류 비율이 50% 초과
외부 연동과 DB 연동
외부 연동과 트랜잭션 처리
두가지 상황 존재
1. 외부 연동에 실패했을 때 트랜잭션을 롤백
2. 외부 연동은 성공했지만 DB 연동에 실패해 트랜잭션 롤백
1) 외부 연동에 실패했을 때 트랜잭션을 롤백
- 외부 연동에 실패했을 때 트랜잭션을 롤백하면 DB에 변경한 데이터가 남지 않는다.
- 읽기 타임아웃으로 인해 외부 연동에 실패했을 때는 별도의 처리가 필요하다.
- 일정 주기로 두 시스템의 데이터가 일치하는지 확인하고 보정하는 방법
- 일정 시간 후 성공 확인 API를 호출하여 확인하고, 성공 응답이 오면 트랜잭션을 지속하고, 실패 응답이 오면 트랜잭션을 롤백한다.
_
2) 외부 연동은 성공했는데 DB 연동에 실패해서 트랜잭션을 롤백
- 취소 API를 호출해 외부 연동을 이전 상태로 되돌리는 것이 필요하다.
- 최소 API가 없다면, 일정 주기로 데이터가 맞는지 비교하는 프로세스가 필요하다.
외부 연동이 느려질 때 DB 커넥션 풀 문제
- 트랜잭션 내부에서 외부 연동을 하면, 커넥션을 물고 있는 시간이 늘어나고 이는 커넥션 고갈로 이어질 수 있다.
- 트랜잭션 외부에서 해도 무관한 외부 연동은 외부에서 하는 게 좋다.
연동 서비스 이중화
- 결제 같이 중요한 서비스는 이중화 하여 장애로 이어지지 않게 대비를 해야 한다.
비동기 연동, 언제 어떻게 써야 할까
동기 연동과 비동기 연동
비동기 방식으로 연동해도 크게 문제가 되지 않는 예시
- 쇼핑몰에서 주문이 들어오면 판매자에게 푸시 보내기
- 학습을 완료하면 포인트 지급하기
- 컨텐츠를 등록할 때 검색 서비스에도 등록하기
- 인증 번호를 요청하면 SMS로 인증 메시지 발송하기
비동기 방식 연동의 특징
- 연동에 약간의 시차가 생겨도 문제가 되지 않는다.
- 일부 기능은 실패했을 때 재시도가 가능하다.
- 연동에 실패했을 때 나중에 수동으로 처리할 수 있는 기능도 있다.
- 연동에 실패했을 때 무시해도 되는 기능도 있다.
비동기 연동 방식
- 별도 스레드로 실행하기 (내가 잘 알고 있어서 pass)
- 메시징 시스템 이용하기
- 트랜잭션 아웃박스 패턴
- 배치로 연동하기
- CDC 이용하기
메시징
생산자/소비자 (pub/sub)
- 장점1: 두 시스템이 서로 영향을 주지 않는다.
- 장점2: 확장이 용이하다. A to B 에서 A to B,C로 확장해야 될 때 C가 메시징 시스템을 구독하기만 하면 된다. A를 수정하지 않아도 된다.
메시징 시스템
- 카프카: 높은 처리량을 자랑한다. 초당 백 만 개 이상의 메시지를 처리할 수 있다.
- 래빗MQ: 여러 프로토콜을 지원한다. 클러스터를 통해 처리량을 높일 수 있다.
- 레디스: 메모리를 사용하므로 지연 시간이 짧다. 구독자가 없으면 메시지가 유실된다.
메시지 생성 측 고려사항
- 가장 쉬운 방법은 오류를 무시하는 것이다. 로그를 쌓는 것 처럼 유실되도 서비스에는 문제가 없으면 오류를 무시하고, 데몬 로그를 보고 나중에 수정해도 된다.
- 재시도 한다. 하지만 재시도는 메시징 시스템이 중복 수신을 방지하는 기능을 제공하지 않으면 소비자가 중복 메시지를 알맞게 처리해야 한다. 이럴 때는 메시지에 고유 식별자를 부여하면 해결할 수 있다.
글로벌 트랜잭션도 참고하면 좋다.
메시지 소비 측 고려사항
아래와 같은 이유로 메시지 소비자는 중복해서 처리할 수 있다.
- 메시지 생산자가 같은 데이터를 가진 메시지를 메시지 시스템에 두 번 전송
- 소비자가 메시지를 처리하는 과정에서 오류가 발생해서 메시지 재수신
해결 방법으로는
- 메시지 마다 고유 식별자를 부여하여 동일한 메시지는 처리하지 않는다.
- 멱등성을 갖도록 API를 구현한다. 그럼 두 번 동일한 요청이 와도 결과는 동일하다.
이벤트와 커멘드
- 이벤트: 어떤 일이 발생했음을 알려주는 메시지. 이벤트 메시지는 정해진 수신자가 없다.
- 커멘드: 무언가를 요청하는 메시지. 메시지를 수신할 측의 기능 실행에 초점
궁극적 일관성: 이 모델은 두 데이터 저장소 간의 일관성을 보장하긴 하지만, 즉시가 아닌 일정 시간이 지난 후에야 일관성이 맞춰진다는 특징을 가진다.
트랜잭션 아웃박스 패턴
트랜잭션 아웃박스 패턴은 하나의 DB 트랜잭션 내에서 다음 2가지 작업을 수행한다.
- 실제 업무 로직에 필요한 DB 변경 작업을 수행한다.
- 메시지 데이터를 아웃박스 테이블에 추가한다.
아웃박스 테이블에 쌓인 메시지 데이터는 별도의 메시지 중계 프로세스가 주기적으로 읽어서 메시징 시스템에 전송한다.
배치 전송
메시징 시스템이 거의 실시간으로 데이터를 연동한다면, 배치는 일정 간격으로 데이터를 전송한다.
배치로 전송하는 전형적인 실행 과정은 다음과 같다.
- DB에서 데이터를 조회하고, 조회한 결과를 파일에 기록하고, 파일을 연동 시스템에 전송한다.
재처리 기능 만들기
- 파일을 지정한 시간에 전송하지 못하면, 어떤 이유에서든 전송에 실패하면 일정 시간 후에 재전송하는 기능을 구현해 두자.
- 재시도 했음에도 실패하는 경우도 있다. 이럴 때를 대비해서 수동으로 배치를 쉽게 실행할 수 있도록 명령어나 API를 만들어 두자.
CDC
CDC란 변경된 데이터를 추적하고 판별해서 변경된 데이터로 작업을 수행할 수 있도록 하는 소프트웨어 설계 패턴
CDC가 유용한 경우: 시스템이 복잡해서 연동 코드를 넣기 부담스러울 때 CDC를 유용하게 활용할 수 있다. 신규 시스템을 구축하게 되면, 이전 시스템 데이터를 사용하는 다른 서비스를 위해서 신규 시스템의 데이터를 이전 시스템 데이터베이스에 연동시켜 주어야 하는 경우가 있다. 이럴 때 CDC와 카프카를 활용하여 해결할 수 있다.