서론
최근 리팩토링 작업을 진행하고 있다.
리팩토링 작업을 진행하던 도중 Transaction들이 Chain Transaction Manager로 묶여있는 걸 확인할 수 있었고 해당 Manager가
Deprecated가 된 상태로 있는 이슈가 있어 해당 부분을 개선하고자 했다.
개선 도중 이 내부는 어떻게 동작되고 있고 개선 대안은 어떤 게 있는지 의문점이 들었었고 해당 부분을 개선하는 도중 내부 동작을 기억하고자 글을 적게 되었다.
개선하면서 하나씩 더 채워보려고 한다.
What is Chained TransactionManager?
위 트랜잭션 매니저는, Spring Data Commons에서 공식적으로 지원하는 라이브러리이다.
해당 기술의 내부 구조를 간단하게 살펴본다면 여러개의 나눠진 트랜잭션 매니저를 하나로 묶어 사용하는 방식이다.
묶인 트랜잭션은 순차적으로 트랜잭션을 실행하고 역순으로 Commit이 진행된다. 말로만 해서는 어떤 느낌인지 감이 안 온다. 한번 그림과 내부 구현체를 보면서 살펴보자
간단하게 다이어그램으로 흐름도를 파악해보았으니, 내부 구현체를 통해 어떻게 진행되는지 한번 파악해보고자 한다.
내부 파헤쳐보기
public 메인
기본적으로 각 체이닝을 위해 선언된 PlatformTransactionManger 배열들이 파라미터로 요청이 들어왔을 때,
해당 매니저들과 매니저들의 리소스 동기화를 위해, SyncronizationManager을 함께 생성함수로 전달하고 있음을 확인할 수 있다.
주 생성 함수
해당 주 생성 함수에서는 Assert를 통해 Null safety 하게 관리하기 위해 검증하고 있음을 확인할 수 있다.
그렇다면 어떻게 역순으로 커밋 또는 롤백이 진행되고 있을까?
Commit
commit 내부를 보니, 각 선언된 Managers를 reverse를 통해 역순으로 실행하고 있음을 확인할 수 있다.
내부 동작원리는 다음과 같은 순서대로 진행됨을 확인할 수 있었다.
- TransactionManager List를 역순으로 순회하며 commit 또는 rollback을 수행한다.
- commit이라는 flag를 중점으로 분기처리를 진행한다. 만약, 예외가 발생한다면 flag를 false로 바꾸고 rollback을 수행한다.
- commit Flag를 활용한 분기 외, 각 두 개의 if문에서는 다음과 같은 행위를 수행한다.
- 새로운 동기화가 등록될 경우, 동기화를 해제한다.
- 커밋 중 예외가 발생한다면 예외처리가 수행된다.
예외처리 시, 다음과 같은 구조로 실행되고 있음을 확인할 수 있다.
- 첫 번째 트랜잭션 매니저가 실패했는지 여부를 파악한다.
- 트랜잭션 상태를 결정해 HeuristicCompletionException를 발생시킨다.
- HeuristicCompletionException을 던져서 트랜잭션의 최종 상태와 예외를 전달합니다.
Rollback
다음은 Rollback 메서드의 내부를 살펴보자
Rollback의 경우, MultiTransactionStatus를 롤백하고 각 트랜잭션 매니저에 롤백을 시도한다.
순서는 다음과 같다.
- 롤백 중 예외 처리를 위한 변수들을 초기화한다.
- 트랜잭션 매니저 목록을 역순으로 순회하며 롤백을 수행한다.
- 롤백 중 예외가 발생한다면, 위 선언된 rollbackException에 이슈에 대한 기록과 함께 발생한 Manager를 기록한다.
그 후, 각 if문을 통해 아래와 같은 수행을 진행합니다.
- if (multiTransactionStatus.isNewSynchonization())
- 만약, 새로운 동기화가 있다면, 동기화를 해제시킨다.
- if (rollbackException!= null )
- Exception이 존재한다면 UnexpectedRollbackException을 발생시킨다.
commit, rollback 두 개 다 reverse라는 메서드를 통해 역순으로 동작이 이뤄진다. 해당 메서드는 private 메서드로 선언되어 있음을 확인할 수 있었다.
그렇다면, 왜 reverse를 통해 역순으로 트랜잭션을 처리할까?
공통적인 이유는 다음과 같다.
- 트랜잭션 매니저 체인이 중첩이 발생되어 있을 때, 롤백 및 커밋이 역순으로 수행되어야 하기 때문
그와 별개로 커밋과 롤백 시 역순으로 진행되는 이유는 각각 다음과 같다.
- Commit 시
- Inner Transaction Commit Order
- 내부 트랜잭션 매니저 체인에서는 외부에서 내부로 중첩된 트랜잭션이 실행된다.
- 외부 트랜잭션 매니저가 먼저 커밋되어야 내부 트랜잭션 매니저가 커밋될 수 있다.
- 따라서, 내부 트랜잭션 매니저에서는 역순으로 커밋을 진행하여 내부에서 외부로의 순서를 맞춥니다.
- Inner Transaction Commit Order
- Rollback 시
- Inner Transaction Rollback Order
- 마찬가지로 내부 트랜잭션 매니저 체인에서는 외부에서 내부로 중첩된 트랜잭션이 실행된다.
- 외부 트랜잭션 매니저에서 롤백이 발생하면, 내부 트랜잭션 매니저에서도 롤백되어야 한다.
- 따라서, 내부 트랜잭션 매니저에서는 역순으로 롤백을 진행하여 내부에서 외부로의 순서를 맞춘다.
- Inner Transaction Rollback Order
Spring Transaction 전파 원리를 생각해 보며 다시 생각해 보니 당연한 이야기임을 알 수 있었다.
Warning! 문제가 있어요!
Deprecated issue
현재 ChainedTransactionManager는 Deprecated 가 되어있는 상황이다. 이는 2.5 버전 이후부터는 사용되지 않음을 확인할 수 있었다.
완벽한 트랜잭션 보장이 될 수 없다.
해당 그림을 다시 한번 살펴보고 내부 소스를 다시한번 생각해 보자
각 트랜잭션들은 체이닝 되어 관리가 되고 있고, 보장 순서도 역순으로 보장이 이뤄지고 있다.
다음과 같은 요구사항을 두고 한번 더 생각해 보자
admin (Start) -> auth (Start) -> Logic -> auth (COMMIT) -> admin (ROLLBACK)
auth 이 COMMIT 된 이후에 admin 가 ROLLBACK이 된다면 auth은 이미 COMMIT이 되었기에 ROLLBACK 되지 않아 데이터에 문제가 발생
즉, 트랜잭션의 원자성에 어긋나는 결과가 발생할 수 있다.
그렇기에, 체이닝을 할 때는 순서가 중요하다.
해당 순서를 통해 알 수 있듯 Rollback은 나중에 선언된 트랜잭션에서 에러가 발생할 경우 Rollback 보장이 이뤄지기에 에러가 날 확률이 높은 트랜잭션을 후순위 Chain으로 묶어줘야 안전한 트랜잭션을 구성할 수 있다.
그렇다면 대안은 어떤 게 있을까?
이에 대한 대안으로는 다음과 같은 방법을 생각해보았다.
- JTA Transactional
- SAGA Pattern
- OutBox Pattern
해당 방법 하나씩 테스트를 진행해 보며 적합한 방법을 찾아보고 적용해 보며 자세하게 더 포스팅을 해보려고 한다.
REF
https://taes-k.github.io/2020/06/09/chained-transaction-manager/
https://github.com/spring-projects/spring-data-commons/issues/2232
https://wannaqueen.gitbook.io/spring5/spring-boot/undefined-1/39.-jta-by-ys
더 간편하게 내부 코드를 보시기 위해서는 아래 링크를 참조하시면 됩니다.
'Spring' 카테고리의 다른 글
Feature Flags? 누구냐 넌 (1) | 2024.02.19 |
---|---|
실시간 코인시세 어디까지 알아봤니? part 1 (1) | 2023.08.14 |
빗썸 API를 활용한 매수 / 매도 데이터 적재 (1) | 2023.06.11 |
[Spring Boot + Chat GPT] Open AI API 적용기 (1) | 2023.04.16 |
분산락과 네임드락 그리고... 동시성 (1) | 2023.02.02 |