공연을 예매하는 결제 로직을 구현하고 transaction
을 적용시켜 포인트 확인, 예매할 좌석의 유무, 테이블 작성 등 결제 로직에 필요한 과정들을 하나의 트랜잭션으로 묶어서 일련의 과정을 한 개의 로직으로 처리 되게끔 데이터의 일관성을 유지하고자 했다.
트랜잭션이 잘 동작하는지 확인하기 위해서 이미 예매된 좌석을 예매하는 상황을 테스트 해봤는데,
롤백도 잘 적용되는 것 같고 실제 좌석 테이블에는 새로운 컬럼이 작성되지 않았다.
2개씩 예매하는 요청이었고, 2개의 예매 내역과 4개의 좌석 예매 컬럼들이 작성되어 있는 상태였을 때 위와 같이 좌석 테이블에는 컬럼이 추가되지 않았다.
하지만 예매 테이블에는 2개만 있어야 하는 컬럼들이 하나 더 생성되어 3개가 되었다.
과정을 보아하니 롤백이 되면 결제 로직 전부가 생성이 되지 않고 롤백되어야 하지만 예매 내역을 생성하는 로직이 그대로 커밋이 되었다.
이러한 예상 했던 과정과 다른 부분을 찾기 위해서 콘솔을 찾아 보던 중 이상한 점이 하나 발견 되었다.
맨 윗줄을 보면 내가 생성한 트랜잭션이 정상적으로 시작됨을 알 수 있지만 밑에 부분에서 따로 만들어주지 않았던 트랜잭션이 한 번 더 실행되고 그 로직이 커밋이 되어 예매 table이 생성되는 것을 알 수 있다.
해당 로직이 실행되는 곳을 찾아보니.
여기 임을 알 수 있는데, 여기서 생각하게 된 것이 Repository entity에 접근하는 쿼리를 사용했기 때문에 transaction이 따로 한 번 더 되는 것이 아닌가 하고 생각하게 되었다. 따라서 쿼리가 트랜잭션 작업 안에서 이루어지기 위해서 Repository
가 아닌 queryRunner
의 메서드를 이용하여 질의를 하는 방식으로 바꿔야 한다고 생각 했다.
이렇게 바꿔주고 실행 한 결과는
좌석 테이블
예약 테이블
아까 처럼 예약 테이블에 컬럼이 추가되지 않고 정상적으로 예외 상황이 생겼을 때 commit
되지 않고 rollback
이 정상적으로 되는 것을 확인 할 수 있다.
콘솔에서도 아까와는 달리 transaction
이 직접 설정해준 곳 외에서 한 번 더 transction
일어나는 상황은 생기지 않았다.
동시성 처리를 위해서 로직에서 어떤 조치를 취해야 할 지 고민이 되었다.
예매가 일어나는 로직 시작 하기 전에 해당 좌석이 이미 예약된 좌석인지 확인하는 로직과, 결제 마무리 후 transaction commit
되기 전에 또 다시 좌석이 이미 예약된 좌석인지 확인하는 로직을 한 번 더 진행 하면서 확인해주는 작업을 해준다면, 동시에 같은 좌석에 예매가 이루어 지는 상황은 오지 않을 수 있지 않을까라는 생각을 했었다.
또한 동시성 처리의 격리 수준을 적절하게 해주기 위해서 transaction 공부할 때 배웠던 격리 수준을 따로 설정해주는 방법도 생각이 났었다.
Transaction 격리 수준
READ UNCOMMITTED
- 커밋 되지 않는 읽기를 허용
READ COMMITTED
- 커밋 된 읽기만을 허용
REPEATABLE READ
- 읽기를 마치더라도 공유락을 풀지 않으며, 트랜잭션이 완전히 종료될 떄 까지 락을 유지
SERIALIZABLE
- 데이터를 읽는 동안 읽기도 불가능
- 가장 높은 수준의 결리 수준, 동시성이 떨어지는 문제점 존재
다음과 같은 격리 수준이 있는데 내 프로젝트에 알맞는 격리 수준을 선택해서 삽입하기로 했다
격리 수준을 설정하는 것은 너무 간단했다.