개발 공부/프로젝트

이상 거래 탐지 트러블슈팅 - StackOverflowError부터 @Transactional 롤백까지

baby-t 2026. 4. 24. 11:09

구현은 무사히 마쳤지만, 실제 테스트를 진행하면서 예상치 못한 문제들이 연속으로 터졌습니다. 이번 포스팅에서는 그 트러블슈팅 과정을 기록합니다.


1. 트러블슈팅: StackOverflowError

구현 후 테스트를 진행하니 콘솔에 다음과 같은 에러가 발생했습니다.

Plaintext
Caused by: java.lang.StackOverflowError
    at DefaultedRedisConnection.pExpire
    at DefaultedRedisConnection.pExpire
    at DefaultedRedisConnection.pExpire
    ...

redisTemplate.expire() 호출 시 무한 재귀가 발생하는 현상이었습니다.

원인은 의존성 충돌이었습니다. 기존에 redisson-spring-boot-starter를 쓰고 있었는데, 이 라이브러리가 내부적으로 spring-boot-starter-data-redis를 포함하면서 DefaultedRedisConnection이 중복으로 등록되어 충돌이 발생한 것입니다.

💡 해결: 의존성 분리

Gradle
// Before
implementation 'org.redisson:redisson-spring-boot-starter:3.x.x'

// After
implementation 'org.redisson:redisson:3.x.x'
implementation 'org.springframework.boot:spring-boot-starter-data-redis'

스타터(starter) 대신 redisson 코어와 spring-boot-starter-data-redis를 분리해서 명시적으로 주입하니 깔끔하게 해결됐습니다.

2. 트러블슈팅: @Transactional 롤백 문제

이상 거래로 차단됐을 때 해당 주문을 FAILED 상태로 DB에 저장하려 했는데, 저장이 전혀 안 되는 문제가 있었습니다. 주문 내역을 봐도 차단된 기록이 남지 않았어요.

원인은 Spring의 @Transactional 기본 롤백 정책이었습니다. RuntimeException이 발생하면 트랜잭션 전체가 롤백되기 때문에, orderRepository.save()가 정상적으로 실행되었더라도 throw ex 시점에 데이터가 같이 롤백되어 날아간 것이죠.

💡 해결: noRollbackFor 옵션 적용

Java
// Before
@Transactional
public void createOrder(Long userId, OrderRequestDto dto) { 
    // ... 
}

// After
@Transactional(noRollbackFor = AbnormalTradeException.class)
public void createOrder(Long userId, OrderRequestDto dto) { 
    // ... 
}

noRollbackFor 옵션으로 AbnormalTradeException은 롤백 대상에서 제외하도록 설정했습니다. 이제 이상 거래로 차단된 주문도 FAILED 상태로 거래 내역에 정상적으로 기록됩니다.

3. 프론트엔드 에러 메시지 개선

기존에는 주문 실패 이유에 관계없이 항상 똑같은 메시지가 떴습니다.

JavaScript
// Before
alert("주문에 실패했습니다. 서버를 확인해 주세요.");

서버에서 "단시간 과다 주문으로 차단되었습니다"라는 구체적인 에러 메시지를 내려줘도 프론트엔드에서 이를 무시하고 있었던 거예요. 이를 동적으로 받아오도록 수정했습니다.

💡 해결: 응답 메시지 파싱

JavaScript
// After
const message = error.response?.data?.message || "주문에 실패했습니다.";
alert(message);

이제 사용자에게 "단시간 과다 주문으로 차단" 같은 서버의 구체적인 메시지가 정확하게 전달됩니다.

4. 최종 동작 확인

유저 맞춤 메시지로 수정

5. 마무리

이번 작업을 통해 금융 도메인에서 이상 거래 탐지가 단순한 '기능 추가'가 아니라는 걸 뼈저리게 느꼈습니다.

보안과 UX를 고려해 차단과 알림을 구분하는 설계 결정, Redis Sorted Set을 활용한 Sliding Window Rate Limiting 구현, 그리고 실무에서 정말 자주 마주치는 트랜잭션 롤백 정책 문제까지. 실제 금융권에서 고민할 법한 문제들을 깊이 있게 경험하고 해결할 수 있었던 뜻깊은 프로젝트였습니다.