ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 2025.11.01~2 외부API 5
    TIL 2025. 11. 1. 17:50

    외부 API를 호출하는 로직에서는 다양한 실패 상황을 고려해야 한다.

    생각해볼 수 있는것들이 많다.

    단순한 예제로 외부 호출 후 DB작업을 한다고 해보자. 여러가지를 고려할 수 있다.

     

    응답

    외부 호출은 성공과 실패뿐 아니라 알 수 없음의 상태도 고려해야 한다.
    알 수 없음은 timeout 이후 실제로는 처리되었을 가능성이 있기 때문이다.
    이 경우 다시 조회를 통해 결과를 확인할 수 있다.
    다만 timeout이 반복될 수 있으므로, 재시도 시에는 backoff를 두는 것이 적절하다.

     

    트랜잭션 범위

    트랜잭션 내부에서 외부 api를 호출하는 것은 여러 자원을 점유한다.
    db 커넥션 풀과 애플리케이션의 스레드를 점유하고, 락을 함께 사용 중이라면 락 대기 시간이 외부 api의 응답 시간에 영향을 받는다.

     

    호출 순서

    외부 api 호출 순서는 데이터 정합성에 영향을 준다.

     

    외부 api를 db 작업보다 먼저 호출하는 경우

    db 작업에서 실패하면 db는 롤백되지만 외부 api는 롤백되지 않는다.
    이 경우 최종 일관성을 맞추기 위해 실패 큐에 작업을 쌓아 재시도할 수 있다.
    외부 api가 멱등하다면 엔트리 포인트부터 다시 실행하는 것도 가능하지만, 이는 외부 시스템에 부하를 줄 수 있다.

     

    또 다른 방법으로 외부 api에 취소 요청을 보내는 것도 있다. 단순한 결제 취소 api 호출이 여기에 해당한다.
    하지만 catch 블록에서 단순히 호출하면, 취소 api가 실패하는 경우도 고려해야 한다.
    이벤트를 발행해 다른 스레드에서 취소를 처리할 수도 있지만, 이벤트 발행 자체가 실패할 수도 있다.
    이벤트 발행을 보장하기 위한 트랜잭션 아웃박스 패턴 같은 방법이 있지만, 결국 db 적재 자체도 실패할 수 있다.
    모든 것은 실패할 수 있다는 전제를 두어야 한다.

     

     

    외부 api를 db 작업보다 나중에 호출하는 경우

    트랜잭션 내부에서 외부 호출이 실패하면 롤백으로 이어지므로 컨트롤은 쉽다.
    하지만 앞서 말한 트랜잭션 범위의 문제와 알 수 없음 응답을 처리해야 하는 복잡함이 있다.
    트랜잭션 밖에서 호출하면 범위 문제는 없지만, 보상 트랜잭션과 수동 롤백을 처리해야 하고 여전히 알 수 없음 응답은 남는다.

     

    결국 외부 api와 db 업데이트만으로도 고려할 점이 많고,
    여기에 외부 api가 여러 개인 경우는 더욱 복잡해진다.
    이런 문제는 msa 구조에서 자주 발생하며, 많은 회사가 saga 패턴을 사용한다.

     

     

     

     

    결제 설계

    이렇게 여러 케이스를 고려해야한다.

    결제 상태를 관리하는 테이블을 만들고 insert-only 방식으로 동작시킨다.
    사용자 요청이 들어오면 결제 대기 상태를 insert한다.
    이후 결제 api를 호출하고, 성공·실패·알 수 없음 응답을 처리한다.

     

    성공이나 실패는 각각 상태를 insert하면 된다.
    db insert가 실패할 수도 있으므로 그에 대한 대응도 필요하다.
    알 수 없음 응답의 경우는 상태를 insert하지 않고 사용자에게 응답을 돌려준다.
    사용자는 재시도를 할 수 있는데, 이때 마지막 상태가 결제 대기라면 “대기 중” 메시지를 보여준다.
    나중에 다시 확인하라고 안내해야 고객 이탈을 막을 수 있다.
    이 방식으로 이중 결제를 방지할 수 있다.

     

    실패 및 알 수 없음 대응

    다만 다음 두 가지 상황을 대응해야 한다.

    • 외부 api는 성공했지만 db insert가 실패한 경우
    • 외부 api timeout 등으로 결과를 알 수 없는 경우

    이 두 경우 모두 현재 상태는 결제 대기 상태로 남게 된다.
    pg사 내부에서 성공했다면 콜백으로 결제 성공을 알릴 수 있다.
    그 콜백으로 결제 성공 상태를 insert하면 된다.
    콜백 실패 시 재시도 횟수 제한이 있을 수 있으며, 최종 실패 시 이메일 등으로 통보받을 수 있다.
    pg사 어드민에서 수동으로 재처리할 수도 있다.

    db insert 실패나 pg사 네트워크 오류, timeout으로 실패한 경우는
    백그라운드 배치나 알림 시스템으로 모니터링해야 한다.
    배치에서는 일정 시간 이상 결제 대기 상태로 남은 주문을 조회하고,
    pg사에 상태를 조회해 성공·실패 여부에 따라 insert한다.

     

     

    상태 테이블

    상태 테이블의 설계도 고민할 지점이다.
    현재 장부 테이블이 insert-only 방식으로 동작하며 일부 상태 역할을 하고 있다.
    하지만 결제 대기 같은 중간 상태는 없기 때문에 구조를 바꿔야 한다.
    장부와 상태 테이블이 유사해 관리 포인트가 늘어날 수 있다.
    이 부분은 더 고민이 필요하다.

     

    예를 들어 timeout으로 사용자에게 실패를 안내했지만, 그 사이 콜백으로 결제가 성공 처리된 경우
    사용자가 재시도할 때 이미 결제가 완료되었다는 응답을 줘야 한다.
    이런 흐름을 처리하기 위해 상태 테이블의 플로우를 명확히 설계해야 한다.

     

    [ 결제 대기 → 결제 성공/실패 → 결제 취소 대기 → 결제 취소 성공/실패 ]

     

     

     

     

    뭐 이렇게 비슷하게 되지 않을까

    결제

     

    환불
    결제 + 환불

    재요청 시 처리

     

    결제 요청 시 결제 대기를 insert하고, timeout 등으로 대기 상태가 유지되면
    사용자 재요청 시 현재 상태를 확인해 안내한다.
    결제 성공은 콜백에서 처리하며, 취소 성공·실패 이후에는 다시 결제를 시작할 수 없게 한다.
    즉 결제 가능한 상태는 결제 상태가 없거나 이전 상태가 결제 실패일 때뿐이다.

     

    • empty : 결제 시작 가능. 환불 시작 불가
    • 결제 시작: 결제 시작 불가능, 결제중 메시지 반환. 환불 시작 불가
    • 결제 실패 : 결제 시작 가능. 환불 시작 불가
    • 결제 성공: 결제 시작 불가능. 환불 시작 가능 
    • 환불 시작 : 결제 시작 불가능. 환불 시작 불가능
    • 환불 성공 : 결제 시작 불가능. 환불 시작 불가능
      • 부분 환불 고려해야함. 그렇다면 환불 가능 
    • 환불 실패 : 결제 시작 불가능. 환불 시작 가능

     

     

     

    잘못된 요청 (예: 결제 전 환불 요청)

    클라이언트가 결제 전에 환불 요청을 보냈다고 가정하자.
    이때 검증 전에 환불 대기 상태를 insert할지,
    먼저 검증 후 정상인 경우에만 insert할지를 결정해야 한다.

    즉시 insert하면 테이블 관리가 복잡해지고, 모니터링으로 감지하기도 어렵다.
    따라서 요청 검증 → 상태 insert 순서가 적절하다.
    (이전 상태가 결제 성공이 아닌데 환불 요청이 왔다면 insert하지 않는다.)

     

     

    콜백

    비즈니스 로직은  pg사의 콜백으로 처리하려 했다.

    그럼 pg사가 왔을 때의 상태 테이블은 결제대기 혹은 결제성공 이 두가지일 수 있다.

    이전 상태를 보고 상태 insert유무를 결정하고 이후의 비즈니스 로직을 처리해야한다.

    또 콜백은 중복 처리가 될 수 있으니 멱등하게 처리되어야한다.(예:PG사가 timeout을 받은 경우, PG사 내부 로직의 버그) 비즈니스 로직이 멱등해야한다는 것이다.

    이또한 고민되는 부분이다.

     

    멱등키

    db에서 관리될 멱등키 생성 방식을 정의해야 한다.
    결제 요청 단위로 고유한 키를 생성하고, 이를 통해 중복 처리를 방지한다.

    혹은 주문key로만 관리하던지.

     

     

    모니터링

    가장 마지막 상태가 결제 시작, 환불 시작에서 장시간 머물러 있던 건들을 pg사에 조회 후 상태를 변경해야한다.

    5분 이전에 생성된 상태라면 재시도를 하는 것이다.

     

     

    https://junuuu.tistory.com/818

     

    https://haon.blog/article/toss-slash/safely-network-handling-errors/

     

    'TIL' 카테고리의 다른 글

    2025.11.05 ab test  (0) 2025.11.05
    2025.11.04 insert select not exists / event sourcing, current, history  (0) 2025.11.05
    2025.10.29 외부API 4  (0) 2025.10.29
    2025.10.28 외부API 3  (0) 2025.10.28
    2025.10.27 외부API 2  (0) 2025.10.27

    댓글

Designed by Tistory.