외부 API 호출을 트랜잭션 안에 넣는 개발자에게
by 그릿 | GROWTH_ESSAY | 2026-11-03
#백엔드 #트랜잭션 #성장 #사유멘토링을 하다 보면 주문이나 결제 로직에서 흔히 마주치는 코드가 있습니다. 메서드 위에 @Transactional을 붙여두고, 그 안에서 외부 결제 PG사 API나 알림 발송 모듈을 그대로 호출하는 구조입니다. 코드는 깔끔하게 읽히고, 예외가 나면 롤백되니 안전해 보입니다.
그런데 과연 그럴까요? 아닙니다. 이 한 줄이 서비스 전체를 멈추게 만듭니다.
트랜잭션은 데이터베이스 커넥션을 쥐고 있는 시간입니다
200명이 넘는 백엔드 주니어들의 프로젝트를 리뷰하면서 가장 아슬아슬했던 순간이 바로 이 지점입니다. 트랜잭션이 시작되는 순간 스프링은 커넥션 풀(HikariCP)에서 DB 커넥션을 하나 꺼내옵니다. 그리고 트랜잭션이 끝날 때까지 그 커넥션을 놓지 않습니다.
내부 연산과 DB 쿼리는 보통 몇 밀리초(ms) 단위로 끝납니다. 반면 외부 네트워크 호출은 다릅니다. 상대 서버의 일시적 지연으로 2초, 3초 동안 응답이 늦어지면 어떻게 될까요? 아무 일도 하지 않는 DB 커넥션이 그 시간 내내 묶여버립니다.
커넥션 풀에 준비된 10개, 20개의 커넥션은 순식간에 동납니다. 결제뿐만 아니라 메인 페이지 조회, 로그인 요청까지 모든 사용자의 요청이 줄줄이 대기 상태에 빠집니다. 외부 서비스 하나의 지연이 데이터베이스 전체 고갈로 번지는 순간입니다.
외부 시스템은 롤백되지 않습니다
더 치명적인 문제는 정합성입니다. 데이터베이스는 롤백할 수 있지만, 이미 전송된 외부 API 요청은 되돌릴 수 없습니다.
외부 PG사로 결제 승인 요청을 보냈습니다. 결제는 정상적으로 완료되어 고객의 계좌에서 돈이 빠져나갔습니다. 그런데 바로 다음 줄의 DB 저장 로직에서 데이터 제약조건 위반으로 예외가 터집니다. 스프링은 트랜잭션을 롤백합니다.
결과는 어떻게 됩니까? DB에는 주문 내역이 하나도 남아있지 않은데, 고객은 결제 완료 문자를 받습니다. 돈은 빠져나갔는데 주문은 사라진 유령 주문이 탄생합니다. 데이터베이스의 원자성은 내 DB 안에서만 유효할 뿐, 바깥 네트워크까지 책임져주지 않습니다.
네트워크 경계를 트랜잭션 밖으로 밀어내십시오
해결책은 기술의 기교가 아닙니다. 경계를 의식하는 태도입니다.
DB 커넥션이 필요한 작업과 네트워크를 타는 작업을 한 바구니에 담지 마십시오. 먼저 외부 API를 호출하기 전에 결제 대기 상태를 짧은 트랜잭션으로 저장하고 커넥션을 반납하십시오. 그 뒤 트랜잭션이 없는 상태에서 외부 API를 호출하십시오. 응답이 돌아오면 결과를 반영하기 위해 다시 짧은 트랜잭션을 열면 됩니다.
지켜야 할 기준은 단순합니다. 비싼 데이터베이스 커넥션을 물고 있는 채로, 통제할 수 없는 외부 네트워크의 응답을 멍하니 기다리게 만들지 마십시오.
코드를 짤 때 스스로에게 질문을 던져야 합니다. 지금 이 순간 내 코드가 물고 있는 자원은 무엇인가? 그 질문에 답할 수 있을 때 백엔드 개발자의 진짜 감각이 자라납니다.