어플리케이션에서, 동시성 이슈를 막기 위해 DB Lock을 활용 해야 할 일이 있었습니다.
이 때, Spring에서 사용하는 @Transcational 이 적용된 코드를 개선하여 성능향상을 고려하였습니다.
기본 개념을 재 정립한 후, 성능을 향상 시킬 부분을 고민 하기로 하였습니다.
따라서 이 글은 Transcational 의 기본 개념과 Spring AOP를 활용한 @Transcational에 대한 글입니다.
Transaction의 컴퓨터 과학분야에서의 의미는 “더 이상 분할이 불가능한 업무처리의 단위”를 의미합니다
간단하게 제가 자주 사용하는 postgreSQL에서 DB에서의 트랜잭션을 보겠습니다.
BEGIN TRANSACTION;
##작업1
select * from tb_order;
##작업2
update tb_order set client_name = 'ggg' where tb_order.id = 1;
COMMIT
위의 예시처럼 트랜잭션을 이용하여서 여러 작업을 모아서 하나의 단위로 보는 것이 트랜잭션의 개념입니다.
트랜잭션의 특징으로 대게 ACID라고 불리는데, 조금 더 구체적으로 특징에 대해 알아보도록 하겠습니다.
#cf) 트랜잭션의 롤백 명령의 경우, DDL문은 처리 대상이 아닙니다.
격리수준은 동시에 여러 트랜잭션이 처리 될 때, 트랜잭션끼리 얼마나 격리되어 있는지를 나타내는 것을 의미한다.
즉 트랜잭션이 다른 트랜잭션에 변경한 데이터를 어느범위까지 볼 수 있게할지를 결정하는 것을 의미한다.
총 4가지의 격리수준이 있다.
위에서 아래로 내려갈수록 트랜잭션 간 고립 정도가 높아지며 성능이 떨어진다.
( trade off 관계라 생각하면 된다.)
내가 사용하는 PostgreSQL 기본값을 뭐일까? ⇒ READ COMMITED
이다
자 여기까지 기본적인 Transcation의 개념을 알아보았다.
이제 우리가 Spirng에서 사용하는 DB접근방식 통해 어떻게 Transaction을 관리하는지 알아보자
Spring에서의 DB접근 방법 중 JDBC각 제일 Low Level에 존재한다 이것들을 추상화해서 여러개의 접근방법이 존재하는데 대표적으로 Mybatis, JPA, JDBC Template 등이 있다.
이 때, Transcation 관련된 코드도 각각 다른데, 이것을 해결하기 위해 Spring은 트랜잭션을 관리하는 코드도 추상화해서 제공한다.
이 때, 사용되는 것이 PlatfromTranscationManager이고, 각 접근방법에 따라 구현체들도 제공한다. 거기다가, 코드들도 전부 AOP를 활용해 실제 비지니스 로직만 작성하면 되게끔 만들어 놓았다. (얼마나 편한가.. 제대로 알기만 하면, 우리는 가져다가 쓰기만 하면된다.)
김영한 선생님 강의에서 나온 사진을 통해 트랜잭션의 작동순서가 어떻게 되는지 정리해보자.
#cf) 트랜잭션 동기화 Thread에서 공유되는 ThreadLocal이란 곳에 저장해두고 사용한다고 하는데.. 이후에 자세히 알아보도록 하자.
특정 클래스나 메소드에 하나라도 @Transcational 어노테이션이 있으면, 트랜잭션 AOP는 해당 클래스를 상속받아서 프록시 객체를 만들어서 스프링 빈 컨테이너에 등록한다.
이렇게 되면 어떤 class를 DI를 통해 주입될 때, Spring DI를 통해 프록시 객체가 주입되므로, 메소드를 호출할 때, 프록시 객체가 먼저 실행되고 이후에 작동하게 된다.
프록시를 거치지 않고 대상 객체를 직접 호출하는 경우, AOP가 적용되지 않는다.
즉 class 내부의 메소드에 의해 호출하는 메소드에 @Transcational이 있더라도, 적용이 되지 않는다.
왜? → class 외부에서 맨 처음 class 접근할 때, aop가 적용되지 않은 상태에서 호출하게 되면, 상속받은 class(xxxx$$CGLIB) 에서 → class(xxxx) 로 호출해서 로직이 실행되고 그 안에서, 내부 메소드가 실행되기 때문에, 내부메소드에 aop를 달아놔도 spring aop가 처리된 상속된 class를 실행시키지 않는다.
@Transcational 어노테이션에서 사용할 수 있는 옵션들
1 TrasncationManager
2 rollbackfor / rollbackForClassName / noRollbackFor /
3 isolation
4 timeout
5 readOnly
왜? 이런 규칙을 default로 설정하고, 필요에 따라서 내가 설정을 하게끔 했을끼?
스프링은 기본적으로
위의 이유로 @Transcational의 기본값이 이렇게 된 것이다..
→ 어떤 시스템을 구성할 때, 예외를 2가지로 나누어서 생각해야한다.
트랜잭션이 둘 이상 있을 때, 어떻게 작동할까??
트랜잭션 전파 기본
스프링은 다양한 트랜잭션 전파 옵션이 있다.
default == REQUIRED 일 경우
외부 트랜잭션 수행중인데, 내부 트랜잭션이 추가로 수행되는 경우
원칙 : 하나의 트랜잭션으로 생각한다. ( 아래의 룰을 읽어보면 이해된다.)
→ 모든 논리트랜잭션이 커밋되어야 물리 트랜잭션이 커밋된다.
→ 하나의 논리 트랜잭션이라도 롤백되면 물리 트랜잭션은 롤백 된다.
내부에 트랜잭션 실행하더라도, 가장 외부의 트랜잭션이 물리 트랜잭션을 컨트롤하게끔 제어한다.
REQUIRES_NEW 을 사용하게 되면, 다른 트랜잭션을 만들고, 별도의 트랜잭션 동작을 하게된다.
→ 커넥션을 2개 들고 사용한다고 생각하면된다..
( 한 요청의, 커넥션을 2개들고 있는 문제…db connection 개수가 부족해질 수 있다…)
옵션 보통 REQUIRED , REQUIRED_NEW 두가지를 가장 많이 사용한다.
평소에 사용하던 Transcational을 다시 한번 정리해보았다. 기존에 대략적으로 알고 있던 내용들을 다시 한번 정리한 계기가 되었다.
이제 성능 향상을 위해 어떤 것들을 할지 생각해보도록 하자.
(김영한 선생님의 DB 강의)
https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-db-2/dashboard