"고객의 돈은 정상적으로 빠져나갔는데, 우리 시스템에는 주문 내역이 없다면?"
현재 구조에서는 에러 발생 시 상태를 이전으로 되돌리는 '안전망(Rollback)'이 작동하지 않아 고객 CS와 신뢰도 하락으로 직결될 위험이 있습니다.
데이터베이스에는 여러 작업이 한 몸처럼 움직이도록 묶어주는 '트랜잭션(Transaction)' 기능이 있습니다.
하나라도 실패하면 전체를 취소(Rollback)하여 사고를 막아줍니다.
현재 시스템은 주문 생성, 재고 차감, 로그 저장 등이 완전히 단절된 채 쪼개져서 실행됩니다. 중간에 에러가 나도 앞의 작업이 취소되지 않아 데이터가 꼬이게 됩니다.
결제 완료 후 일어나는 모든 DB 작업을 하나의 거대한 트랜잭션으로 묶어, 무조건 다 같이 성공하거나 다 같이 실패하도록 보호해야 합니다.
이벤트로 다건의 사은품을 지급하는 applyPromotionEventOrder 로직을 생각해보겠습니다.
개발자의 의도는 "사은품 중 하나라도 지급에 실패하면 전체를 취소(Rollback)하자" 였을 것입니다.
하나의 트랜잭션으로 묶여있지 않기 때문에 롤백은 일어나지 않습니다.
결국 에러로 인해 시스템 프로세스는 중단되었지만, 이미 DB에 입력된 1번, 2번 사은품 내역은 그대로 남아 불완전한 상태의 데이터가 영구적으로 저장됩니다.
사실 코드를 살펴보면 나름대로 사은품 발급 실패 시 데이터 오염을 방지하기 위한 흔적이 있습니다.
앞서 본 applyPromotionEventOrder 로직 상단에는 @Transactional이 선언되어 있었습니다.
하지만 이는 데드 코드(Dead Code)였습니다.
어노테이션이 작동하려면 설정 파일에 <tx:annotation-driven>이 존재해야 하지만 시스템엔 누락되어 있습니다.
대신,
구형 방식인 XML AOP 설정에 의해 강제로 트랜잭션이 묶여 있는 상태입니다. 코드 보기
클릭하여 닫기 ×
즉, 현재 시스템은 이름이 "Tx"로 끝나는 메서드만 무조건 트랜잭션을 거는 구형 XML 방식을 사용 중입니다.
이로 인해 불필요한 단일 쿼리에도 트랜잭션이 발동하는 낭비가 발생하고 있습니다.
단일 INSERT, UPDATE, DELETE 쿼리는 DB의 Auto-Commit으로 스스로 원자성이 보장되므로 스프링 트랜잭션이 무의미합니다.
데이터 변경이 없는 단순 조회(SELECT)임에도 트랜잭션 세션이 생성되어 불필요한 트랜잭션 세션 오버헤드 및 커넥션 점유 시간이 증가하여 리소스가 낭비됩니다.
~Tx 규칙 때문에 쿼리 1회 실행을 위해 SET autocommit=0과 COMMIT 통신이 추가되어 Round-trip이 3배로 증가합니다.
추가 전수 검사 결과, 내부 데이터 정합성 문제와는 별개로
외부 결제(PG) 연동 인터페이스에도 @Transactional이 선언되어 있는 것을 확인했습니다.
만약 이 어노테이션이 실제로 작동했다면,
외부 API 대기 시간과 맞물려 시스템 인프라 전반에 걸친 심각한 병목 현상을 유발했을 것입니다.
아이러니하게도 방금 본 PG 결제 로직의 @Transactional 조차 설정 누락으로 인한 데드 코드였기 때문에,
우리는 커넥션 풀 고갈이라는 시스템 다운 장애를 피할 수 있었습니다.
* 결과적으로 데이터 정합성은 깨졌지만, 시스템 셧다운은 면한 기이한 상태가 유지된 것입니다.
이 거대한 안전망을 구축하고 커넥션 풀을 보호하기 위한 아키텍처 개편안입니다.
외부 네트워크 통신(PG사 결제)과 내부 데이터베이스 작업을 철저하게 분리(Facade Pattern)해야 합니다.
[추가 보고] 기능 정합성 검토 외에, 추가 전수 조사 중 포착한 인프라 리소스 누수 현상입니다.
아래의 단계별 카드(탭)를 클릭하시면, 실제로 작동하고 있던 설정 및 소스코드를 확인하실 수 있습니다.
이 문제는 비즈니스 로직(자바 파일) 수정이 일절 필요치 않은 순수 스프링 XML 설정상의 제거 작업입니다.
매우 낮은 리스크로 커넥션 리소스 효율성을 극대화할 수 있습니다.
dataSourceSub, txManagerSub 사용처 0건sqlSessionFactorySub 사용처 0건
컨트롤러 메서드(예시: /guest/cart-migration) 진입 시 선언된 @Transactional이 무시되어,
단 하나의 트랜잭션으로 묶여야 할 로직이 메서드 단위로 쪼개져 총 5회의 트랜잭션이 제각각 실행되는 실증 로그
로그인 이력 병합 메서드(saveLoginHistoryTx) 실행 시 단 한개의 MERGE 쿼리만 날림에도
물리 커넥션 2개(주 DB + 매출 DB)가 동시 점유되는 실증 로그
@Transactional 데드 코드를 전수 정리 (물리 영향도 0)Service 메서드로 안전하게 묶음<tx:annotation-driven> 설정을 활성화하고, 추출한 신규 Service 메서드에만 어노테이션을 부여하여 제한적(Targeted)으로 안전하게 활성화dataSourceSub, txManagerSub, sqlSessionFactorySub 빈 정의를 제거하여 리소스 낭비 원천 차단context-transaction.xml에서 requiredSubTx AOP 설정을 제거하여 이중 커넥션 점유 현상을 근본적으로 해결
단기 조치로 검증된 어노테이션 기반 트랜잭션을 전체 서비스 레이어로 점진적으로 확대 적용합니다.
추후 서비스 분리나 멀티 데이터소스 도입 시, 이중 커넥션 점유 장애가 재발하지 않도록 명확한 개발 가이드라인을 수립합니다.
단기 조치에서 매출 DB AOP를 제거한 데 이어, 주 DB용 구형 AOP(requiredTx) 설정까지 완전히 걷어냅니다.
명칭 규칙(~Tx) 기반의 자동 결합 방식을 끝내고, 필요한 곳에만 @Transactional을 선언하는 명시적 체계로 전환을 완료합니다.