개발놀이터

트랜잭션 전파 (feat. 논리 트랜잭션, 물리 트랜잭션) 본문

JPA/JPA

트랜잭션 전파 (feat. 논리 트랜잭션, 물리 트랜잭션)

마늘냄새폴폴 2023. 6. 25. 10:05

이번 포스팅에선 트랜잭션 전파에 대해서 알아보도록 하겠습니다. 

 

사실 트랜잭션 전파에 대해서는 어느정도 알고 있는 부분이 있었는데 제가 아는 수준은 "트랜잭션 전파를 이용하면 데이터 정합성을 유지할 수 있고 그 단계에는 일곱가지 (REQUIRED 외 6개) 가 있다."

 

그리고 각각의 전파 단계에 대해서 학습을 했는데요. 그러다 오랜만에 인프런에 들어갔는데 사놓고 안들은 강의가 있었습니다. 김영한님의 DB 접근기술 2탄이었습니다. 이걸 왜 안봤지? 싶어서 봤는데 정말 흥미로운 내용이 있었습니다.

 

왜 트랜잭션을 전파할 수 밖에 없었는지, 전파 도중 롤백이 일어나면 어떻게 처리하게 되는지에 대한 내용을 상세하게 알려주셨습니다. (역시 영한님!)

 

이번 포스팅에선 그 내용에 대해서 제 언어로 정리하기 위해 적는 포스팅입니다. 더 자세한 내용은 영한님의 스프링 데이터 접근기술 2탄을 참고해주세요!

 

 

트랜잭션 전파

스프링에서는 트랜잭션을 이용해 유연한 데이터베이스 연산 처리를 할 수 있었습니다. 

 

JPA는 이러한 트랜잭션과 함께 쓰기지연으로 상당히 유연한 처리를 할 수 있습니다. 하지만 트랜잭션이 가진 한계도 분명히 존재했습니다. 

 

복잡하게 꼬여있는 애플리케이션 코드들을 사용하기 위해 너도나도 @Transactional 어노테이션을 이용해 트랜잭션을 걸었습니다. 

 

    @Transactional
    public void 어쩌고저쩌고로직() {
        // 대충 뭐 조회하는 로직
        // 대충 조회 했던 거 가공하는 로직
        // 대충 가공하고 저장하는 로직
    }
    
    @Transactional
    public void 어쩌고저쩌고로직() {
        // 대충 뭐 조회하는 로직
        // 대충 조회 했던 거 가공하는 로직
        // 대충 가공하고 저장하는 로직
    }

 

그러다보니 트랜잭션이 꼬이는 문제가 발생했고 한번 꼬이기 시작하니까 걷잡을 수 없는 문제가 발생했습니다. 

 

 

이렇게 트랜잭션 A 안에서 트랜잭션 B를 시작하게 되면 만약 트랜잭션 B에서 예외가 터져 롤백을 하는 경우 상당히 복잡해지게 됩니다. 

 

A도 롤백 해야하나? B만 롤백하나? 그럼 A에서 롤백하면 B는? 이게 두개니까 망정이지 세개 네개 이렇게 쌓이다보면 정말 끝도없이 복잡해질 수 있었습니다. 

 

이 문제는 스프링이 반드시 해결해야 하는 문제였습니다. 때문에 스프링에선 논리 트랜잭션, 물리 트랜잭션이라는 개념으로 해결할 수 있었습니다. 

 

 

논리 트랜잭션, 물리 트랜잭션

논리, 물리 트랜잭션의 개념은 간단합니다. 여러개의 트랜잭션이 겹쳤다면 그걸 아우르는 하나의 트랜잭션으로 묶는다는 개념이었습니다. 

 

맨 처음 외부 트랜잭션에서 호출한 트랜잭션과 그 다음 호출한 내부 트랜잭션을 묶는 하나의 트랜잭션으로 이 문제를 해결했고 하나로 묶는 트랜잭션을 물리 트랜잭션, 안쪽에 있는 트랜잭션들을 논리 트랜잭션으로 명명했습니다. 

 

논리 트랜잭션은 실제 데이터베이스에 영향을 주는 트랜잭션이 아닌 말 그대로 논리적인 단위입니다. 안에서 트랜잭션이 작동하긴 하지만 결과적으로 데이터베이스에 영향을 주는 트랜잭션은 물리 트랜잭션이라는 말입니다. 

 

이렇게 논리, 물리 트랜잭션을 도입하면서 생긴 큰 변화는 다음과 같습니다. 

 

  • 트랜잭션 커밋과 롤백을 일관성있게 할 수 있음
  • 모든 논리 트랜잭션이 커밋이 되어야 물리 트랜잭션이 커밋이 됨
  • 하나의 논리 트랜잭션이라도 롤백을 하면 물리 트랜잭션이 롤백 됨

 

그런데 트랜잭션은 한번 커밋하거나 롤백하면 종료되는데 어떻게 트랜잭션 여러개가 커밋되거나 롤백될 수 있는거죠? 

 

트랜잭션의 커밋 / 롤백 과정

이제 트랜잭션이 롤백되는 상황이 어떻게 진행되는지 알아볼겁니다. 

 

사실 모든 논리 트랜잭션이 커밋되면 물리 트랜잭션이 커밋된다는 개념은 정말 간단합니다. 하지만 롤백은 단순하지 않습니다. 

 

한번 알아보죠. 

 

Case 1. 모든 트랜잭션이 커밋하는 경우

트랜잭션이 시작되는 지점인 외부 트랜잭션은 커넥션 풀에서 새로운 커넥션을 맺고 트랜잭션을 시작합니다. 이렇게 만들어진 외부 트랜잭션은 한가지 설정을 합니다. 

 

isNewTransaction() 설정을 true로 설정하는 것이죠. 

 

논리 트랜잭션들 중 트랜잭션에 편입된 내부 트랜잭션은 해당 설정이 false로 되어있습니다. 이 설정이 true로 되어있으면 언제 물리 트랜잭션이 커밋해야 하는지 알 수 있습니다. 

 

어떻게 알 수 있을까요? 

 

구조적으로 봤을 때 외부 트랜잭션이 트랜잭션을 시작하고 내부 트랜잭션이 커밋을 먼저 하고 다음 외부 트랜잭션이 커밋하게 되어있습니다. 

 

 

맨 처음 커밋을 진행하는 트랜잭션 D는 isNewTransaction 속성이 false이기 때문에 커밋을 하지 않고 그냥 지나갑니다. 이건 C, B도 마찬가지이구요. 

 

실제로 isNewTransaction 속성이 true이 트랜잭션 A가 커밋할 때 데이터베이스에 반영이 됩니다. 

 

Case 2. 외부 트랜잭션에서 롤백하는 경우

이 경우도 어렵지 않습니다. 앞서 설명했듯이 물리 트랜잭션의 실행을 담당하는 것은 isNewTransaction 속성이 true인 트랜잭션을 따릅니다. 

 

만약 외부 트랜잭션인 A 트랜잭션에서 롤백이 일어나면 isNewTransaction 속성이 true이기 때문에 물리 트랜잭션이 이 속성을 보고 트랜잭션을 커밋할지 롤백할지를 정합니다. 

 

지금은 트랜잭션 A에서 예외가 터져서 롤백을 진행해야 하니 물리 트랜잭션은 쉽게 롤백해버립니다. 

 

Case 3. 내부 트랜잭션에서 롤백하는 경우

이 경우는 어떡하죠? 

 

앞서 설명했듯이 isNewTransaction이 false인 내부 트랜잭션은 물리 트랜잭션의 연산에 어떤 역할도 하지 않습니다. 그냥 넘어가게되죠. 

 

그런 내부 트랜잭션에서 예외가 터져서 롤백을 진행해야 한다면 물리 트랜잭션은 롤백해야 합니다. 왜냐하면 하나라도 롤백을 해야하면 모든 트랜잭션이 롤백해야 하기 때문이죠. 이는 트랜잭션의 원자성의 개념하고 일맥상통합니다. 

 

이럴 때를 대비해서 스프링은 rollback-only라는 속성을 가지고 있습니다. 

 

내부 트랜잭션에서 롤백을 진행해야 할 때 내부 트랜잭션은 물리 트랜잭션에 영향을 주지 않기 때문에 그냥 넘어가되 rollback-only 속성을 true로 바꾸고 지나갑니다. 

 

이렇게 되면 로직은 다음과 같습니다. 

 

  1. 외부 트랜잭션에 의해 트랜잭션 시작
  2. 트랜잭션 D, C 커밋 isNewTransaction이 false이므로 지나감 
  3. 트랜잭션 B 롤백 isNewTransaction이 false이므로 지나감, 지나가면서 rollback-only를 true로 설정
  4. 트랜잭션 A 커밋 isNewTransaction이 true이므로 물리트랜잭션에 적용 이 때 rollback-only가 true네! => 물리 트랜잭션 롤백

이런 느낌입니다. 

 

이렇게 진행하면서 스프링은 트랜잭션을 하나로 묶은 상태에서 커밋과 롤백을 자유자재로 넘나들 수 있게 된 것입니다. 

 

Case 4. 그런데 저는 예외 터진 부분만 롤백하고 나머지는 커밋하고 싶은데요?

JPA @Transactional의 전파 단계인 REQUIRES_NEW로 설정하면 됩니다. 

 

위의 예제에서는 트랜잭션 A, B, C, D가 모두 하나의 물리 트랜잭션으로 연결되어 있습니다. 이걸 여러개로 나누기만 하면 됩니다. 

 

이렇게 되면 물리 트랜잭션 B만 롤백이 되고 나머지는 커밋이 될겁니다. 

 

트랜잭션 B에만 REQUIRES_NEW 속성을 넣게 되면 트랜잭션 B를 생성할 때는 기존 트랜잭션을 잠시 밀어두고 새로운 트랜잭션을 생성하고 그 트랜잭션이 끝나면 기존 트랜잭션을 실행합니다. 

 

물론 이렇게 하면 장점도 있지만 단점도 있습니다. 바로 데이터베이스 커넥션이 두개가 생긴다는 것입니다. 

 

때문에 사용자 요청이 얼마 되지도 않는데 커넥션이 부족해서 데이터베이스가 허덕이고 있을 수도 있다는 얘기이죠. 그러니 조심해서 사용해야 합니다. 

 

 

마치며

이렇게 트랜잭션 전파에 대해서 알아봤습니다. 이야... 정말 깊이있는 내용이었습니다. 감탄을 자아낼 수 밖에 없었는데요. 

 

제가 기존에 알고있던 트랜잭션의 상식들이 하나둘씩 채워지는 느낌이라 정말 좋은 기분이었습니다. 여러분도 영한님의 데이터베이스 접근 기술 2탄을 한번 들어보시면 어떨까요? 

 

 

출처

스프링 DB 2편 - 데이터 접근 기술 활용 (지식공유자 : 김영한)