시작하기

외부 결제 게이트웨이 응답 지연 시 트랜잭션 타임아웃 처리의 모호성

증상 확인: 결제가 멈췄는가?

결제 버튼을 누른 고객이 “결제 처리 중”에서 멈춰 있다. 당신의 백엔드 로그에는 외부 PG사(Payment Gateway)로의 요청이 성공적으로 나갔지만, 응답을 30초, 60초 동안 기다린 끝에 ReadTimeoutException이 발생한다. 이때. 고객의 카드 결제는 실제로 어떻게 되었는가? 이 모호한 상태가 시스템에 남아, 재고는 감소했는데 주문은 생성되지 않거나, 중복 결제가 발생하는 재앙을 초래한다.

원인 분석: 네트워크의 불확실성과 비즈니스 로직의 경합

핵심 원인은 네트워크 통신의 본질적 불확실성에 있다. 당신의 애플리케이션이 PG사에 결제 승인 요청을 보낸 직후, 네트워크 라우터의 순간적 장애, PG사 내부 처리 지연, 또는 방화벽의 이상 동작으로 인해 응답 패킷이 유실되거나 극도로 지연될 수 있다. 이 상황에서 애플리케이션에 설정된 소켓 타임아웃이 먼저 도래하면, 당신은 “결과를 알 수 없는” 상태에 빠진다. 이 모호성(Uncertainty)을 제대로 처리하지 않는 비즈니스 로직이 바로 두 번째 원인이다.

해결 방법 1: 타임아웃 재시도와 동기화된 상태 관리

가장 기본적이면서도 필수적인 조치다. 단순히 타임아웃을 길게 설정하는 것은 서비스 응답성을 해친다. 대신, 안전한 재시도 메커니즘과 상태를 확실히 관리하는 로직을 구현해야 한다.

  1. 멱등성(Idempotent) 설계 확보: 모든 결제 요청은 고유한 order_id 또는 payment_key를 포함시켜야 한다. PG사는 동일한 키로 중복 요청이 와도 최초 한 번의 처리만 수행하도록 협의되어 있어야 한다. 이는 재시도의 안전망이다.
  2. 타임아웃 및 재시도 정책 설정: HTTP 클라이언트(예: Apache HttpClient, OkHttp)에서 연결 타임아웃(ConnectionTimeout)과 읽기 타임아웃(ReadTimeout)을 분리 설정한다. 일반적으로 읽기 타임아웃은 10~15초로 설정하고, 최대 1~2회 재시도한다. 재시도 시에는 지수 백오프(Exponential Backoff) 방식을 적용하는 것이 좋다.
  3. 결제 상태 머신 구현: 데이터베이스에 결제 상태를 “승인요청”, “승인성공”, “승인실패”, “미확인”으로 명시적으로 관리한다. 타임아웃 발생 시 상태를 “미확인”으로 기록하고. 별도의 보상 트랜잭션(compensating transaction) 프로세스를 트리거한다.

해결 방법 2: 비동기 웹훅과 배치 조회를 통한 확정

Method 1만으로는 PG사의 최종 응답을 확신할 수 없다. 따라서 비동기적인 수신 경로와 주기적인 확인 절차를 병행해야 한다.

  1. 웹훅(Webhook) 엔드포인트 필수 구축: PG사가 최종 처리 결과(성공/실패)를 당신의 서버로 비동기적으로 발송(콜백)할 수 있는 HTTPS 엔드포인트를 반드시 운영한다. 이 웹훅 수신 로직은 멱등하게 처리되어야 하며, “미확인” 상태를 확정된 상태로 업데이트한다.
  2. 정기 배치 조회 프로세스 운영: 매 5~10분마다 “미확인” 상태인 결제 건들에 대해 PG사의 “거래 조회 API”를 호출하는 배치 작업을 실행한다. 웹훅이 유실될 경우를 대비한 최후의 안전장치다. 조회 결과로 상태를 확정하고, 알림을 생성한다.
  3. 임시 성공 응답과 상태 안내: 사용자 경험을 위해, 타임아웃이 발생했지만 요청 자체는 정상 발송된 경우, 사용자에게 “결제가 정상적으로 접수되었습니다. 완료 여부는 알림으로 안내드리겠습니다.”와 같은 임시 성공 메시지를 줄 수 있다. 이는 결제 창에서 무한 대기시키는 것보다 낫다.

배치 조회 시스템 설계 포인트

배치 조회는 단순 CRON 작업이 아니라, 내결함성을 고려해야 한다.

  • 한 번의 배치 실행에서 너무 많은 건을 조회하지 않도록 페이징 처리.
  • PG사 조회 API 호출 실패 시 재시도 및 회피 로직 포함.
  • 배치 실행 이력과 오류 로그를 상세히 기록하여 모니터링.

해결 방법 3: 아키텍처적 접근: 아웃박스 패턴과 이벤트 드리븐

대규모 트래픽과 높은 일관성이 요구되는 시스템에서는 아키텍처 수준의 해결책이 필요하다. 이는 모놀리식 애플리케이션보다는 MSA 환경에 적합하다.

  1. 아웃박스 패턴(Outbox Pattern) 도입: 결제 요청 명령을 처리할 때, ‘PG사 요청’ 이벤트를 로컬 데이터베이스의 아웃박스 테이블에 먼저 원자적으로 저장한다. 이후 별도의 메시지 릴레이 프로세스가 이 테이블을 폴링하여 PG사에 요청을 보낸다. 이렇게 하면 애플리케이션 프로세스가 죽어도 요청 자체는 유실되지 않는다.
  2. 이벤트 드리븐 상태 흐름: 결제의 각 단계(요청, 타임아웃, 웹훅 수신, 조회 결과)를 이벤트로 발행한다. 주문 서비스, 정산 서비스, 알림 서비스는 각자 필요한 이벤트를 구독하여 자신의 상태를 업데이트한다. 타임아웃 이벤트를 구독한 ‘결제 조회 서비스’가 자동으로 배치 조회를 수행하도록 구성할 수 있다.
  3. 서킷 브레이커(Circuit Breaker): PG사의 API 상태가 불안정하여 연속 타임아웃이 발생하면, 서킷 브레이커가 작동하여 일정 시간 동안 모든 요청을 즉시 실패 처리한다. 이는 시스템의 자원 고갈을 막고, PG사에 대한 연쇄적 부하를 방지한다.

주의사항: 절대 지켜야 할 안전 수칙

결제 요청 시 발생하는 타임아웃은 네트워크 지연이나 PG사의 일시적 장애 등 다양한 변수에 의해 발생합니다. 이러한 상황에서 타임아웃을 즉각적인 결제 실패로 간주하여 사용자에게 재결제를 유도하는 것은 중복 결제의 가장 큰 원인이 됩니다. 실제로 대규모 결제 시스템의 운영 사례를 살펴보면, 타임아웃 이후 수 초 이내에 PG사로부터 승인 결과가 뒤늦게 도착하는 경우가 빈번하게 확인됩니다. 따라서 시스템은 타임아웃 발생 즉시 상태를 ‘미확인’으로 전환하고, 웹훅(Webhook)이나 배치 작업을 통한 후속 조회를 거쳐 최종 상태를 확정해야 합니다. 이 과정에서 트랜잭션 ID와 주문 번호를 포함한 상세한 로그를 ERROR 레벨로 남기는 것은 향후 보상 트랜잭션 처리를 위한 필수적인 데이터 자산이 됩니다.

전문가 팁: 성능과 안정성을 동시에 잡는 숨은 설정

PG사 API를 호출할 때는 HTTP 커넥션 풀을 적극적으로 활용해야 합니다. 매 요청마다 새로운 SSL 핸드셰이크를 수행하면 심각한 지연이 발생할 수 있으므로, Apache HttpClient 기준으로 maxTotal, defaultMaxPerRoute 값을 실제 트래픽 패턴에 맞게 튜닝하고 유휴 커넥션 검증(setValidateAfterInactivity)을 반드시 활성화해야 합니다. 이를 통해 연결 재사용 시의 불안정성을 줄일 수 있습니다. 더 나아가 PG사가 지원한다면 gRPC와 같은 지속 연결 기반 프로토콜을 검토하는 것도 좋은 선택인데, 이는 평균 응답 속도를 크게 개선해 타임아웃 발생 가능성을 근본적으로 낮춰줍니다. 이러한 연결 관리 전략은 로그 파일 로테이션 실패 시 디스크 공간 포화와 서비스 중단처럼 사소해 보이는 관리 실패가 대규모 장애로 이어지는 상황을 예방하는 것과 같은 맥락에서 중요합니다.

마지막으로, 모든 보상 로직(배치 조회, 상태 정정)은 반드시 멱등성을 전제로 설계되어야 한다. 네트워크는 믿을 수 없으므로, 어떤 보상 작업도 중복 실행되어도 같은 결과를 내야 시스템의 일관성이 유지된다.

더 많은 정보가 필요하신가요?

NFT Ledger 전문팀이 도움을 드리겠습니다.

홈으로 문의하기