티스토리 뷰

우리는 스프링부트로 프로젝트를 할 때 습관적으로 @Transactional 어노테이션을 적용하고 있다. 이 어노테이션을 왜 사용하는지, 언제 사용하는지 알고 사용하면 로직의 흐름을 좀 더 이해하기 수월할 것이다. 그럼 같이 알아보자. 

 

보안

여기서 이야기 하는 보안은 안정성이라고 보면 된다.

어떤 프로젝트나 기능을 만들 때 가장 걱정하는 것이 보안일 것이다. 실제로 보안을 강화하기 위해 다양하게 지원하고 있다. 

HTTP에서 HTTPS, CSRF 문제, UDP와 TCP, Spring Security, 쿠키와 세션 대신 토큰을, 토큰을 더 강화하기 위한 Refresh 토큰 등등.. 지금 생각나는 것들만 적었지만 훨씬 더 많을 것이다. 트랜잭션도 보안 때문에 생겨난 것이라고 보면 된다. 

 

데이터베이스를 신뢰할 수 있을까 ?

예를 들어보겠다. 아래는 프로그래밍 언어들의 정보가 담겨있는 테이블이다. 

 

 

만약 각 프로그래밍 언어들의 나이를 조회하고자 할 때는 아래와 같다.

 

 

하지만 데이터베이스에서 쿼리를 날릴 때 올바른 응답을 줄 것이라고 어떻게 확신하고 사용하고 있을까 ? 우리는 "당연히 올바른 값을 조회하겠지" 라고 생각할 수 있지만 어떠한 장애로 인해 아래와 같이 잘못된 답을 내려줄 수 있지 않을까 ? 

 

Ruby의 나이가 조회되지 않았다.

 

하지만 트랜잭션을 알면 신뢰할 수 있게 된다. 데이터베이스에서는 트랜잭션을 조작함으로써 사용자가 완전히 신뢰할 수 있도록 하고 있다. 

 

트랜잭션이란 ? 

더 이상 나눌 수 없는 가장 작은 하나의 단위를 의미한다. 

모든 데이터베이스들은 기본적으로 트랜잭션을 지원하고 있는데, 위에서도 말했다시피 명령을 온전하게 실행하기 위해서이다. 

그런데 왜 뜻이 더 이상 나눌 수 없는 가장 작은 하나의 단위인 것일까 ? 한번 예시를 들어보겠다. 

 

나는 3만원이 있다. 그리고 친구한테 만원을 보낸다고 가정해보자. 만원을 입력하고 송금 버튼을 누르면 다음과 같은 일이 진행된다. 

 

  1. 먼저 내 계좌에 만원보다 많은 금액이 있는지 확인한다. 
  2. 3만원이 있으니 금액을 전송한다. 
  3. 내 계좌에 만원을 차감한다. 
  4. 친구의 계좌에 만원이 더해진다. 

 

이 과정은 절대로 분리되어서는 안된다. 또 일부만 실행되어서도 안된다. 이렇게 절대로 깨져서는 안되는 하나의 작업을 트랜잭션이라고 하는 것이다. 그럼 데이터베이스는 트랜잭션을 어떻게 신뢰하고 있는 것일까 ? 트랜잭션은 4가지 성질을 바탕으로 신뢰를 보장한다. 

 

  • 원자성
  • 일관성
  • 지속성
  • 독립성

 

그럼 하나하나 알아보자. 

 

원자성

아까 예시로 친구에게 만원을 보낸 예시를 다시 생각해보자. 3번까지는 무탈하게 진행이 잘 되었지만 4번과정에서 어떠한 장애가 발생해서 4번 동작이 실행되지 않았다면 무슨 일이 일어날까 ? 친구는 왜 돈을 보내지 않느냐고 뭐라할 것이고, 나는 황당해하며 돈이 깎인 사진을 보내면서 "돈 깎였는데 뭔소리 하는거야" 라고 생각할 것이다. 이렇게 트랜잭션은 절대로 나눠질 수 없는, 깨질 수 없는 원자처럼 하나가 전부 실행되던지, 아니면 아예 실행되지 않던지 해야한다. 즉, 일부만 실행되어서는 안된다. 이 것을 원자성이라고 한다. 

 

롤백, 트랜잭션 커밋

일관성을 이야기 하기 전에 앞에 내용에서 4번 문제가 실패했다면, 어떻게 처리가 되는 것일까? 

4번 문제가 실패하기 이전, 즉 무탈하게 실행되었던 1번부터 3번까지의 동작을 없던 일처럼 되돌려야 한다. 이를 롤백이라고 한다. 만약 4번 동작도 정상적으로 실행되었다면 수정된 내용을 데이터베이스에 반영한다. 이를 트랜잭션 커밋이라고 한다. 

트랜잭션은 무조건 롤백이나 트랜잭션 커밋이 수행되어야지만 종료될 수 있다. 

 

일관성

데이터베이스의 계층관계, 컬럼의 속성등이 항상 일관되게 유지되어야 한다. 만약 나이의 컬럼속성이 문자로 변한다면 ? 심각한 상황이 발생한 것이다. 이 것을 일관성이라고 한다. 

 

지속성

만약 트랜잭션 커밋까지 완료되었다면, 그 다음에 어떠한 장애가 발생하더라도 데이터베이스에 그 커밋되었던 내용이 영원히 지속되어야 하는 지속성이 있다. 트랜잭션 커밋 후 다음 단계에서 장애가 발생했는데, 정상적으로 실행되었던 로직까지 사라진다면 이 전에 수정했던 내용이 없어지게 된다. 

 

이를 위해서 트랜잭션은 로그로 남겨져 어떠한 상황에서도 대비할 수 있도록 한다. 

 

 

데이터베이스에서는 명령이 끝날 때 까지의 수행 내역을 로그에 저장해둔다.  

 

  • Redo 로그 : 데이터베이스에 반영된 내용을 재반영하기 위함
  • Undo 로그 : 수행에 실패해 이전 상태로 되돌리기 위함

 

꼭 트랜잭션 커밋, 롤백을 위한 것같지 않은가 ?

 

독립성

트랜잭션 수행작업 시 다른 트랜잭션이 작업에 끼어들 수 없고 독립적으로 수행해야 한다는 독립성이 있다. 

독립성은 트랜잭션은 격리 수준 설정을 통해 보장한다. 하지만 데이터베이스에 여러 작업이 들어왔는데 독립성 보장을 목적으로 작업들이 하나하나씩 수행하게 된다면 프로그램이 비효율적(느리게)으로 동작할 수 있는 상황이 발생한다. 

그래서 데이터의 무결성과 동시성의 성능을 지키기 위해 트랜잭션의 설정이 중요한 것이다. 

 

스프링 프레임워크에서 지원하는 트랜잭션

다양하게 트랜잭션을 설정할 수 있는데, 간단하게 가장 많이 사용하는 @Transactional 에 대해서만 알아볼 것이다. 

@Transactional 을 선언적 트랜잭션, 트랜잭션 어노테이션이라고 많이 부른다. 메서드, 클래스, 인터페이스에 적용할 수 있다. 여기서는 간단하게 어노테이션이라고 부르겠다. 

 

클래스에 붙였다면 해당 클래스에 존재하는 모든 메서드에 대해서 어노테이션이 적용된다. 만약 어노테이션이 중복되었다면

클래스 메서드 → 클래스, 인터페이스 메서드 → 인터페이스로 우선순위를 갖는다. 인터페이스 메서드는 디폴트 메서드나 static 메서드가 아닐까 싶다. 

 

메서드에 어노테이션을 붙이고 실행하게 될 때, 메서드가 끝날 때 성공적으로 종료되었다면 트랜잭션 커밋, 도중에 문제가 발생했다면 롤백을 진행한다. 대부분 서비스 계층 메서드에 붙인다. 

 

읽기 전용 트랜잭션

마지막으로 읽기 전용 트랜잭션에 대해서 알아보도록 하자. 

 

 

트랜잭션 어노테이션에는 다양한 속성들이 존재한다. 우리는 그 중 readOnly를 알아보자. 

readOnly를 true로 설정하게 되면 트랜잭션 안에서 update, insert, delete 작업이 일어나는 것을 방지한다. 

또한 JPA의 더티체킹 기능을 무시할 수 있어 성능향상에 도움이 된다. 대부분 조회메서드에 붙이거나 클래스에 readOnly = true로 설정한 후 조회로직 메서드가 아닌 메서드에 @Transactional을 붙인다. default 값이 false기 때문이다. 물론 조회로직 메서드가 적다면 반대로 설정해도 상관없다. 

 

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday