트랜잭션 전파

나무·2023년 11월 25일

스프링 DB2

목록 보기
3/4
post-thumbnail

1. 기본적인 트랜잭션 - 커밋, 롤백

여태 우리는 하나의 커넥션에서 하나의 트랜잭션이 발생하는 상황만 고려를 해왔다.

public class Service{
    public void txMethod(){
    	// 첫번째 트랜잭션 수행
        TransactionStatus tx1 = txManager.getTransaction(
        				new DefaultTransactionAttribute());
         
         // ... 서비스로직 수행 ...
         
         // 커밋
        txManager.commit(tx1); 
        
        
        // 두번째 트랜잭션 수행
        TransactionStatus tx2 = txManager.getTransaction(
        				new DefaultTransactionAttribute());
         
         // ... 서비스로직 수행 ...
         
         // 커밋
        txManager.commit(tx2); 
    }
}

트랜잭션매니저는 먼저

  1. 데이터소스로부터 새로운 커넥션을 할당 받고 tx1 이라는 트랜잭션을 생성한다.

  2. 그 다음 서비스로직이 수행된 후 트랜잭션 매니저를 통해 tx1 을 커밋한다. (tx1 종료)

  3. 이제 트랜잭션매니저는 새로운 트랜잭션 tx2 를 생성하고 1~2 를 반복한다.

위와 같은 경우 트랜잭션 매니저는 새로운 커넥션을 할당 받고, 정상적으로 트랜잭션을 마치고(커밋) 그 다음 또 새로운 커넥션을 할당 받는다. 이때 tx1과 tx2 가 할당 받은 커넥션은 서로 다른 커넥션인셈이다. 이는 롤백도 마찬가지이다. tx1롤백 혹은 커밋 을 한다해서 tx2 의 트랜잭션에 전혀 지장을 주지 않는다.

즉, 두 트랜잭션은 완전 별개의 트랜잭션이다.

2. 트랜잭션 전파 - REQUIRED

그렇다면 tx1 이 커밋 혹은 롤백을 하기 전에 tx2 가 생성될경우 어떻게 될까? 이 경우 두 트랜잭션은 따로 분리된 채로 동작을 수행할까? 하나의 커넥션에서 수행될까?

트랜잭션 전파 란?

트랜잭션 전파(Transaction Propagation)는 여러 개의 트랜잭션이 중첩되거나 호출되었을 때, 어떻게 트랜잭션 처리가 이루어지는지를 정의하는 개념을 뜻한다.

트랜잭션의 경우 앞서 보았던 내용과 같이 두 개의 트랜잭션이 개별적으로 수행하는 경우도 있지만, 반대로 하나의 트랜잭션이 끝나기 전에 또 다른 트랜잭션이 수행 될 수도 있다. 즉, 여러 트랜잭션들이 서로 중첩되는 상황이 발생할 수 있다는 뜻이다. 그래서 스프링에서는 이런 경우를 대비해 다양한 트랜잭션 전파 옵션들이 제공되는데 우선 REQUIRED 전파 방식을 살펴보자.

REQUIRED 전파 방식

REQUIRED 전파 방식은 결론부터 말하면,

트랜잭션이 없다면 새로 생성하고, 있다면 기존의 트랜잭션에 합류하게되는 방식

아래 코드를 살펴보자.

외부 트랜잭션과 내부 트랜잭션

	void inner_commit() {
    	// 외부 트랜잭션 시작
        TransactionStatus outer = txManager.getTransaction(new DefaultTransactionAttribute());

		// 내부 트랜잭션 시작
        TransactionStatus inner = txManager.getTransaction(new DefaultTransactionAttribute());
        
        
        txManager.commit(inner); // 내부 트랜잭션 커밋
        txManager.commit(outer); // 외부 트랜잭션 커밋

    }

코드를 살펴보면 현재 outerinner 두 트랜잭션이 시작한 상태이며, outer 가 커밋을 하기 전에 inner 가 새로 시작되었다.

outer 는 제일 처음 시작하는 트랜잭션이다. 트랜잭션매니저는 본인이 관리하는 트랜잭션들을 살펴보고 만일 현재 쓰레드에는 생성된 트랜잭션이 없다면 outer 에 새로운 트랜잭션을 생성해서 반환해준다.

반면에,

inner 는 나중에 시작된 트랜잭션이다. 트랜잭션매니저는 이번에는 이미 outer 가 있으므로 새로운 트랜잭션을 생성하지 않는다. 그냥 innerouter 트랜잭션에 합류시켜버린다.

즉, 트랜잭션이 이미 생성되었다면 새로운 커넥션을 받아서 새로운 트랜잭션을 생성하는게 아니라 그냥 계속해서 하나의 트랜잭션에 포함시켜버리는 것이다. 아무리 많은 내부 트랜잭션들이 생길지라도 그냥 합류시켜버린다.

REQUIRED 의 장점

왜 이렇게 하는걸까?

사실 지금까지는 롤백을 고려하지 않았었는데 이제한번 롤백을 고려해보도록 하자. 그리고 내부 트랜잭션 10개가 추가로 시작되었다고 가정하자. 만일 이런 상황에서 트랜잭션4가 실패하여 롤백된다고 하면, 트랜잭션 4와 연관된 다른 트랜잭션들은 마찬가지로 롤백 되어야한다. 하지만 어떤게 트랜잭션4와 관련이 있는지 파악하기란 사실상 불가능에 가깝다.

그렇다면 서로 트랜잭션간의 복잡한 연관관계를 고려하지말고 그냥 모든 트랜잭션들을 하나의 트랜잭션으로 감싸고 대신에 각 내부 트랜잭션들이 하나라도 롤백을 하게되면 그냥 전체 트랜잭션을 롤백 시켜버리면 어떨까? 좀 더 간단하지 않은가?

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

스프링에서는 이 트랜잭션에 대한 개념을 다음과 같이 구분하였다.

물리 트랜잭션 : 전체 트랜잭션이며 실제 커넥션 을 통해서 시작하고 커밋/롤백을 하는 단위이다.

논리 트랜잭션 : 내부에 추가되는 트랜잭션들을 칭하며 트랜잭션매니저가 관리 하며 트랜잭션 매니저를 통해 커밋하고 롤백을 한다.

내부 트랜잭션 커밋

사실 내부 트랜잭션의 커밋(롤백 말고) 은 물리 트랜잭션에 어떤 영향도 끼치지 못한다. 트랜잭션 매니저는 외부 트랜잭션의 커밋을 확인하고 물리 트랜잭션을 커밋한다.

내부 트랜잭션 롤백

하지만 롤백의 경우 다르다.

내부 트랜잭션이 롤백이 되었다면 트랜잭션 매니저는 물리 트랜잭션을 rollback-only , 즉, 롤백-전용으로 바꿔버린다. 이렇게 되면 외부트랜잭션이 커밋을 할지라도 물리 트랜잭션은 rollback-only 로 표시되어 있기 때문에 롤백이 수행된다.

REQUIRED 동작 정리

그림으로 전파의 동작을 정리해놓았다. 한번 잘 살펴보도록 하자.

요청

응답

3. 트랜잭션 전파 - REQUIRES_NEW

스프링의 트랜잭션 전파 기본 옵션은 REQUIRED 이다. 하지만 이외에도 다양한 옵션들이 있는데 이번에는 REQUIRES_NEW 방식을 살펴보자. (실무에서는 사실상 REQUIRED 만 주로 쓰기때문에 가볍게 살펴보겠다.)

트랜잭션을 따로 따로 관리!

REQUIRES_NEW 는 이름에서 유추할 수 있듯이, 새로운 트랜잭션이 시작되면 하나의 트랜잭션에 합류시키는것이 아닌 새로운 커넥션을 생성해 새로운 트랜잭션을 생성 하는것이다.

따로 따로 커넥션을 사용하기 때문에 만일 내부 트랜잭션이 롤백이 발생하더라도 해당 커넥션만 롤백이 되고 커넥션 풀에 반환된다. 외부트랜잭션에는 아무런 영향을 끼치지 않는다.

내부 트랜잭션이 시작되면 트랜잭션 매니저는 기존(외부) 트랜잭션을 잠시 미뤄두고, 새로운 커넥션을 생성한다. 그리곤 내부 트랜잭션을 시작한다. 만일 내부 트랜잭션이 롤백되면 트랜잭션 매니저는 해당 커넥션만 반환한다. 물론 외부 커넥션은 그대로 동기화매니저에 잘 보관이 되어있다.

REQUIRES_NEW 문제점

REQUIRES_NEW 은 새로운 트랜잭션마다 새 커넥션을 생성하기 때문에 커넥션풀 자원을 많이 소모 하게된다.

4. 다른 트랜잭션 전파 방식

거의 사용하지 않으므로 양심없는 행동이지만 스샷으로 떼우겠다. (죄송합니다ㅠㅜ)

본 포스트는
김영한의 스프링 DB 2편 - 데이터 접근 활용 기술 강의 를 보고 정리했습니다.

profile
🍀 개발을 통해 지속 가능한 미래를 만드는데 기여하고 싶습니다 🍀

0개의 댓글