트랜잭션 분리로 연쇄 장애의 늪에서 탈출하기

최혜미·2026년 3월 24일

트러블슈팅

목록 보기
26/29
post-thumbnail

안녕하세요 오랜만에 글을 쓰네요! 백엔드 개발자 최혜미입니다.

최근 eSIM 플랫폼 서비스를 운영하며 eSIM을 발급해주는 외부 연동사의 시스템 장애가 우리 서버의 마비로 이어지는 아찔한 경험을 했습니다.

단순히 "외부 업체가 느려져서"라고 치부하기엔, 내부 구조를 타고 흐르는 장애 전파 메커니즘이 생각보다 훨씬 치명적이었습니다.
이로 인해 약 2시간 동안 전체 서비스가 중단되는 뼈아픈 시간을 보내야만 했는데요...🫠

이번 글에서는 당시 발생했던 문제를 명확하게 정의해보고, 기존의 데이터 정합성은 유지하면서도 시스템 가용성을 개선했던 과정을 공유해보려 합니다.


1. 문제 정의: 왜 외부 지연이 내부 마비로 이어졌나?

🚨 장애 전파 메커니즘

가장 핵심적인 원인은 'DB 트랜잭션 내 외부 API 호출'이었습니다.

  1. Transaction 시작: 비즈니스 로직 진입 시 Spring의 @Transactional에 의해 DB 커넥션(HikariCP)을 점유합니다.
  2. 외부 API 호출: 커넥션을 보유한 상태에서 외부 연동사에 발급/조회 요청을 보냅니다.
  3. Bottleneck 발생: UCL 서버 지연으로 응답이 오지 않자, 해당 스레드는 커넥션을 반납하지 못한 채 Connection Holding 상태로 무한 대기합니다.
  4. Pool 고갈 및 서비스 마비: 점유된 커넥션이 늘어나며 HikariCP가 고갈되었고, 결과적으로 로그인/조회 등 DB 접근이 필요한 모든 기능이 연쇄적으로 마비되었습니다.

2. 기존 설계의 페인 포인트: 비결정적 외부 지연

기존에 긴 트랜잭션을 유지했던 것은 데이터 정합성을 위한 의도적인 설계였습니다.

  • 비결정적 지연: 공급사 특성상 eSIM 주문 후 실제 정보 조회까지의 시간이 1분에서 10분 이상으로 제각각이며 예측이 불가능했습니다.
  • 중복 발급 리스크: 조회가 완료되기 전 트랜잭션을 종료하면 유저의 재시도로 인해 중복 발급이 발생할 우려가 컸습니다.
  • 설계적 선택: 리스크를 0%로 만들기 위해 조회가 성공할 때까지 트랜잭션을 유지하며 재시도하는 Synchronous Retry 방식을 선택했던 것이었죠.

3. 해결 전략: 상태 기반 선점 및 트랜잭션 격리

기존의 정합성 보장은 유지하되, 자원 점유 문제를 해결하기 위해 아키텍처를 전면 개편했습니다.

① 트랜잭션 분리를 통한 자원 고립

전체 프로세스를 물리적으로 분리된 3개의 짧은 트랜잭션으로 재설계하여 커넥션 점유 시간을 최소화했습니다.

  • Transaction 1 (Check): eSIM 발급 가능 여부 사전 검증 후 즉시 커넥션 반납.
  • Transaction 2 (Marking): issuing_started_at을 기록하여 '발급 중' 상태 선점 후 즉시 커넥션 반납.
  • Transaction 3 (Finalize): 외부 API 조회가 최종 성공한 시점에만 다시 커넥션을 확보하여 결과 저장 및 커밋.

결과적으로 외부 API 응답을 기다리는 긴 시간 동안 우리 서버는 DB 커넥션을 전혀 점유하지 않게 되었습니다!

② Atomic Update 기반의 낙관적 선점

비관적 락 대신 컬럼 기반의 선점 방식을 도입하여 커넥션 없이도 동시성을 제어합니다.

UPDATE product 
   SET issuing_started_at = NOW() 
 WHERE id = :id 
   ...

이 쿼리는 단일 트랜잭션 내에서 '문지기' 역할을 수행합니다. 거의 동시에 들어온 요청 중 단 하나만 업데이트 결과 1을 반환받고 나머지는 0을 받아 즉시 400 에러(중복 요청)를 반환하도록 처리했습니다.

③ Proxy 문제를 해결하기 위한 서비스 분리

동일 클래스 내 셀프 호출 시 @Transactional이 무시되는 AOP 프록시 문제를 해결하기 위해, DB 처리를 전담하는 EsimOrderProcessor를 별도로 분리했습니다. 이를 통해 각 단계의 트랜잭션이 물리적으로 완벽하게 시작되고 커밋됨을 보장했습니다.


흔히 경험이 중요하다고들 말하는데, 이번 사건을 겪으며 그 의미를 비로소 실감했습니다.

막상 개발을 시작하면 견고한 설계와 짧은 마감 기한 사이에서 균형을 잡기가 참 어렵지만, 외부 시스템의 불확실성을 우리 내부 리소스로 감내하려 해서는 안 된다는 귀중한 교훈을 얻었습니다.

이번에 깨달은 것은 정합성을 지키면서도 가용성을 잃지 않는 방법이었지만 앞으로 이러한 고민의 흔적들이 하나둘 모여 더욱 단단한 개발자로 성장할 수 있기를 바랍니다.

0개의 댓글