ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Transaction에 대해서 알아보자
    One Cookie a day 2023. 10. 27. 05:05

    📑 Transaction 이란 무엇일까

    이 질문에 대해서, 데이터베이스의 상태를 변화시키기 위해 수행하는 작업 단위라고만 알고 있었는데, 덧붙여 말하자면 나눌 수 있는 최소한의 작업단위를 의미한다.

     

    예를 들어 '이체'를 생각해보자. 

    • 송금하려는 계좌가 정상 계좌인지 확인한다.
    • 내 계좌에 이체하려는 금액 이상이 남아있는지 확인한다.
    • 해당 계좌에 이체 요청을 보낸다.
    • 요청 완료 응답을 받고 이체가 끝난다. 

    위에 적힌 작업들이 [ 이체 ]라는 일을 하기 위해 필요한 최소한의 작업들이다. 

    그런데, [ 이체 ] 라는 작업 (트랜잭션) 내에서도 계좌 확인 / 보내려는 계좌가 타 은행일 경우에 해당 시스템에 확인 및 송금 요청 이런식으로 내부에 트랜잭션이 나누어져 있다. 

     

     

    📑 Transaction Propagation

    • 트랜잭션의 경계는 하나의 커넥션을 통해 진행되므로, 하나의 커넥션이 생성되고 반납되는 범위 안에 존재한다. 
    • 위의 예시처럼, 한 가지의 서비스에 트랜잭션이 2가지 이상 있을 경우 어떻게 동작하고 처리할 것인가? 
    • 트랜잭션이 이미 있는데, 추가적인 트랜잭션이 발생한다면, 새로 만들지, 기존 트랜잭션에 끼울지, 예외를 던질지 결정해야 한다.
    • 이것이 트랜잭션 전파 속성이다. 

     

    물리 트랜잭션 vs 논리 트랜잭션

     

    물리 트랜잭션 : 실제 데이터베이스에 적용되는 트랜잭션, connection을 통해 commit / roll back하는 단위

    논리 트랜잭션 : 스프링이 트랜잭션 매니저를 통해 트랜잭션을 처리하는 단위 

     

    모든 논리 트랜잭션이 커밋되어야 물리 트랜잭션이 커밋되며, 하나라도 논리 트랜잭션이 롤백되면 물리 트랜잭션도 롤백된다. 

     

     

    📑 PROPAGATION_REQUIRED 

     

    특징 

    • 아직 트랜잭션이 존재하지 않는다면 현재 범위에서 새로 생성하거나, 혹은 더 큰 범위에 존재하는 외부의 트랜잭션에 참여
    • 즉, 물리적인 트랜잭션을 강제한다.
    • 스프링의 기본적인 전파 속성이다.  

    주의사항

    • 외부 트랜잭션에 참여하는 경우 로컬의 격리 수준, time out value, read only flag등을 자동으로 무시하고 외부 범위 특성에 join
    • 그렇기 때문에 격리 수준 다른 트랜잭션에 참여할 때 이렇게 덮어씌워지는 것을 막으려면 validateExistingTransactions flag를 true로 바꾸는것을 고려해볼 것 (이 작업은 내부의 read-write 트랜잭션이 외부의 read-only 트랜잭션에 참여하는 시도도 막아줄 수 있다)
    • 기본적으로 이 범위에 있는 트랜잭션들은 하나의 동일한 물리적 트랜잭션으로 매핑된다.
      • 내부 트랜잭션 범위에서 rollback only를 세팅해두거나(default=false이다) 
      • isGlobalRollbackOnParticipationFailure = true(DEFAULT)로 인해 외부 트랜잭션에도 영향을 미친다.
      • inner scope에서 exception 발생해도 outer transaction은 진행해버릴 수 있기 때문에 이를 방지하기 위해 UnexpectedRollbackException을 발생시켜 내부에서 rollback이 발생하였다고 명확히 알려준다 

    PROPAGATION_REQUIRES_NEW

    📑 PROPAGATION_REQUIRES_NEW

     
    특징 
    • 앞서 언급한 PROPAGATIOM_REQUIRED와 달리, 항상 독립된 물리적 트랜잭션을 사용하며, 외부 범위에 있는 이미 존재하는 트랜잭션에 참여하지 않는다. 
    • 다른 물리적 트랜잭션에 존재하여 독립적으로 commit / roll back이 가능하다. 
    • 또한 inner 트랜잭션은 자신만의 isolation level, timeout, read-only 등의 옵션을 선언할 수 있으며 outer 트랜잭션의 옵션을 상속받지 않는다. 
    주의사항 
    • 외부 트랜잭션의 자원들은 내부 트랜잭션이 데이터베이스 커넥션처럼, 자신의 자원을 획득하는 동안 유지되어 있다. 
    • 이는 커넥션 풀의 고갈로 이어질 수 있고, 잠재적으로 데드락으로 이어질 가능성이 있다.( Ex : 여러 스레드들이 자신의 외부 트랜잭션을 활성화시키고, 내부 트랜잭션들이 그들의 새로운 연결을 획득하기를 기다린다면) 
    • 공식 문서에 따르면, 커넥션 풀 사이즈가 이러한 상황에서도 적절히 유지되도록, 동시 스레드들 수 + 1 넘게 남아있는 것 아닌 이상 이 옵션을 사용하지 말 것을 권고하고 있다. 

     

     

    📑 PROPAGATION_NESTED 

     

    특징

    • 하나의 물리적 트랜잭션 내부에 롤백 가능한 여러 세이브 포인트들을 사용하여 트랜잭션을 중첩(내부에 또 다른 트랜잭션)
    • 내부 트랜잭션들이 자신의 범위에서 롤백을 발생시키고 일부 작업들이 롤백되어도, 외부 트랜잭션은 물리적 트랜잭션을 계속 진행할 수 있다. 

    주의사항

    • 추가로 이 세팅은 JDBC 자원 트랜잭션들에만 동작한다. (JDBC savePoints에 매핑되므로) 
      • This setting is typically mapped onto JDBC savepoints, so it works only with JDBC resource transactions. 

     

     

     

    📑 SUPPORTS

    현재 트랜잭션이 존재한다면 거기에 참여하고, 존재하지 않는다면 non-transactional 하게 수행

     

    주의사항

    • 트랜잭션 동기화를 사용하는 transaction manager의 경우, 아무 트랜잭션이 없는(non_transactional) 것과는 약간 다르다.
    • 동기화가 적용될 트랜잭션의 범위를 정의하기 때문이다.
    • 그 결과 같은 자원(jdbc connection, hibernate session 등)이 지정한 전체 스코프에 공유되며, 트랜잭션 매니저의 동기화 구성에 따라 달라질 수 있다. 

     

    📑MANDATORY

    트랜잭션의 존재 여부를 강제하여 이미 시작된 트랜잭션이 있다면 해당 트랜잭션에 참여하되, 존재하는 트랜잭션이 없을 경우 exception 발생시킨다. 

     

    📑PROPAGTION_NOT_SUPPORTED

    non_transactional하게 동작하고 현재 트랜잭션은 보류시킨다. 

    대신, 이 보류 작업은 JtaTransactionManager (javax.transaction.TransactionManager)에서만 특정하게 적용되며 모든 트랜잭션 매니저에 적용되는 것은 아니다. 

     

    📑PROPAGTION_NEVER

    non-transactional하게 동작할 것을 강제하며, 트랜잭션이 존재한다면 exception을 발생시킨다. 

     

     


     

     

    Transaction Isolation Level 

     

     

    READ UNCOMMITED

    • 다른 트랜잭션의 커밋되지 않은 변경 내역들까지 볼 수 있다. 
    • Dirty Read
      • Transaction A가 아직 커밋 완료되지 않은 Transaction B가 만든 변화들을 읽을 수 있다.  

     

    READ COMMITTED

    • 트랜잭션 시작 전 커밋 완료 된 트랜잭션만 읽을 수 있다.
    • Unrepeatable Read
      • 트랜잭션 A가 데이터를 읽었을 때, 트랜잭션 B가 데이터를 변화시킨다.
      • 트랜잭션 A가 다시 데이터를 읽었을 때, 앞서 읽은 데이터와 다른 경우가 발생한다.
      • 동일 쿼리에 대한 결과가 다르게 나올 수 있다. 

     

    REPEATABLE READ

    • 반복해서 read 수행해도 읽은 값이 변화하지 않는다.  
    • 모든 read마다 트랜잭션 시작한 해당 시점 기준의 스냅숏을 읽는다. 
    • Phantom Read
      • 트랜잭션 A가 쿼리를 수행할 때, 트랜잭션 B가 새로운 행을 삽입 또는 삭제하고 이 작업을 커밋한다.
      • 다시 같은 쿼리를 트랜잭션 A가 수행하면, 새로운 행이 생겨나거나 삭제되는 경우가 발생한다. 

     

    SERIALIZABLE

     

    • 모든 트랜잭션들이 하나의 트랜잭션 내에서 처리되는 것과 같은 높은 고립 수준을 제공한다
    • 그 대신, 동시성 측면에서 성능이 좋지 않다. 
    • 다른 트랜잭션이 접근 시, 에러를 발생시키며 실패한 작업에 대한 처리는(재시도 등) 어플리케이션 단에서 확인 후 처리되어야 한다.

     

    아래 이미지는 오라클 문서에서 정의한 격리 수준에 따라 발생하는 문제들의 가능성 여부이다. 

    https://docs.oracle.com/en/database/oracle/oracle-database/23/adfns/sql-processing-for-application-developers.html#GUID-90BB830A-DC7E-49DD-B778-5FEEE7FDF00F

     

     

     

     

     

    참고 글

     

     

     

     

     

Designed by Tistory.