ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [스프링 5 레시피] 10장: 스프링 트랜잭션 관리
    Spring 2021. 4. 20. 21:10
    반응형

     

     

     

    1. 트랜잭션 관리와 성질

     

    트랜잭션 관리는 엔터프라이즈 애플리케이션에서 데이터 무결성과 일관성을 보장하는 필수 기법입니다. 트랜잭션 관리를 하지 않으면 데이터의 일관성이 깨질 수 있기 때문에 저장되어있는 데이터를 신뢰할 수 없게 됩니다.

     

     

    트랜잭션의 성질은 ACID로 설명할 수 있습니다.

     

    https://towardsdatascience.com/ai-on-acid-an-implementation-of-distributed-transactions-for-ai-applications-25cb8d2ec9b6

     

    • 원자성(Atomicity) : 트랜잭션은 연속적인 작업으로 이루어진 원자성 작업이기 때문에 트랜잭션의 작업은 부분적으로 성공하거나 실패할 수 없고, 전부 다 수행되거나 아무것도 수행되지 않도록 보장합니다.
    • 일관성(Consistency) : 트랜잭션이 실행을 완료하면 커밋되고 데이터베이스는 언제나 비즈니스 규칙에 맞는 일관된 상태를 유지하는 것을 의미합니다. 예를 들어, 무결성 제약 조건이 회원의 이메일은 필수 값이어야 한다면 이를 위반하는 트랜잭션은 중단됩니다.
    • 격리성/고립성(Isolation) : 동일한 데이터를 여러 트랜잭션이 동시에 처리할 경우 데이터가 변질되지 않게 하려면 트랜잭션 수행 시 다른 트랜잭션이 연산에 끼어들지 못하도록 보장하는 것을 의미합니다. 예를들어 어떤 데이터에 대해 트랜잭션 A가 수행 중이라면 트랜잭션은 B,C는 트랜잭션 A가 끝나기 전까지 데이터에 접근할 수 없습니다.
    • 지속성(Durability) : 완료 된 트랜잭션의 결과는 안정적으로 보존되어야 함을 의미합니다. 원자성에 근거해서 성공(Commit) 한 결과를 비휘발성 저장장치에 저장함으로써 전기가 끊어지는 등 시스템 장애가 나더라도 이를 보장해야 합니다.

     

     

     

     

     

     

    2. 스프링 트랜잭션 관리자 구현체

     

    스프링은 여러가지 Transaction Manager API 기능을 추상화했는데요, 덕분에 개발자는 하부 구현체를 자세히 몰라도 스프링이 제공하는 트랜잭션 기능을 이용할 수 있고, 이렇게 작성된 트랜잭션 관리 코드는 특정 트랜잭션 기술에 종속되지 않을 수 있습니다.

     

     

    https://programmersought.com/article/62674627193/

     

     

    예를 들어, JDBC를 사용할 경우엔 DataSouceTransactionManager, ORM 프레임워크를 사용할 경우엔 Hibernate 또는 JpaTransactionManager, JTA를 사용할 경우엔 JtaTransactionManager가 필요한데요, 스프링은 개발자가 작성한 기존 코드를 변경하지 않고 기술에 따라 구현체를 바꿔 사용할 수 있도록 PlatformTransactionManager 인터페이스를 통해 서비스를 추상화(PSA)를 하였습니다.

     

     

     

     

     

     

     

     

    3. 스프링에서 트랜잭션 처리 방법

     

     

    스프링에서는 트랜잭션 처리를 지원하는데 일반적으로 어노테이션 방식으로 @Transactional을 선언하여 사용하며, 이를 선언적 트랜잭션이라 부릅니다.

     

    이 어노테이션은 클래스와 메서드 레벨에 적용이 가능한데요, 클래스 레벨에 선언할 경우 모든 public 메서드에 트랜잭션 처리가 적용됩니다. @Transactional은 AOP 방식으로 트랜잭션을 처리하는 프록시가 생성되어 실제 작업은 Target Method에 위임하기 때문에 프록시가 접근할 수 없는 private 메서드에는 트랜잭션을 적용할 수 없습니다.

     

    스프링은 실제 프록시 내부에서 PlatformTransactionManager를 사용해서 트랜잭션을 얻어와 시작하고, 성공 여부에 따라 Commit 또는 Rollback 합니다.

     

     

     

     

     

     

    4. Propagation (전파 속성)

     

    트랜잭션 동작 도중 다른 트랜잭션을 호출하는 상황에 선택할 수 있는 옵션입니다.

     

    @Transactional의 propagation 옵션을 통해 설정할 수 있는데요, 피호출 트랜잭션 입장에서 자신을 호출한 트랜잭션을 그대로 사용할 수도 있고, 새롭게 트랜잭션을 생성해서 독립적으로 진행할 수도 있습니다.

     

    ▶ REQUIRED (Default)
    - 진행중인 트랜잭션 내에서 실행하며 트랜잭션이 없을 경우 새로운 트랜잭션을 생성한다.

     

    ▶ REQUIRES_NEW

    - 진행중인 트랜잭션을 참여하지 않고 항상 새로운 트랜잭션을 생성한다.

     

    ▶ SUPPORTS

    - 진행중인 트랜잭션이 있으면 이어받아 실행하며, 없을 경우 트랜잭션 없이 실행한다.

     

    ▶ NOT_SUPPORTED

    - 트랜잭션 없이 실행하며, 진행 중인 트랜잭션이 있을 경우 해당 트랜잭션을 잠시 중단시킨다.

     

    ▶ MANDATORY

    -  반드시 트랜잭션 내에서 실행하며 트랜잭션이 없을 경우 새로 생성하는 대신 예외를 발생시킨다.

     

    ▶ NEVER

    -  반드시 트랜잭션 없이 실행하며 진행중인 트랜잭션이 있을 경우 예외를 발생시킨다.

     

    ▶ NESTED

    - 진행중인 트랜잭션의 중첩 트랜잭션 내에서 실행하고, 진행 중인 트랜잭션이 없으면 새 트랜잭션을 생성한다.

     

    중첩 트랜잭션은 트랜잭션 안에 다시 트랜잭션을 만드는 것이다.

     

    하지만 독립적인 트랜잭션을 만드는 REQUIRED_NEW와는 다르다. 중첩된 트랜잭션은 자신을 호출한 트랜잭션의 커밋과 롤백에는 영향을 받지만 자신의 커밋과 롤백은 자신이 참여한 트랜잭션에는 영향을 주지 않습니다.

     

    예를 들어 1,000,000건의 데이터를 처리하면서 작업 도중 끊어서 커밋하는 경우에 유리합니다. 10,000건씩 끊어서 커밋하는 경우, 중간에 잘못되어 트랜잭션이 실패하더라도 1,000,000건 전체가 아니라 10,000건만 소실됩니다.

     

     

     

     

     

     

    5. Isolation (격리 수준)

     

    다수의 트랜잭션이 동시에 실행되는 상황에서 트랜잭션 처리방식을 좀 더 고려해야 합니다.

    예를 들어 특정 트랜잭션이 어떤 레코드에 대한 처리를 하고 있고 아직 커밋되지 않았는데 다른 트랜잭션이 같은 레코드에 접근한다면 다음과 같은 문제가 발생할 수 있습니다.

     

     

     

    ▶ Dirty Read (오염된 읽기)

    - 트랜잭션 A가 어떤 값을 1에서 2로 변경한 후 커밋하지 않은 상황에서 트랜잭션 B가 같은 값을 읽는 경우 2가 조회된다.

    - 하지만 트랜잭션 A가 작업 실패로 롤백될 경우 트랜잭션 B는 잘못된 값을 읽게 된 것이다.

    - 즉, 트랜잭션이 아직 완료되지 않은 상황에서 다른 트랜잭션의 접근을 허용할 경우 발생할 수 있는 데이터 불일치이다.

     

     

    ▶ Non-Repeatable Read (재현 불가한 읽기)

    - 트랜잭션 A가 조회 쿼리로 어떤 값 1을 읽었다. 이후 A는 같은 쿼리를 실행할 예정인데 중간에 트랜잭션 B가 1을 2로 변경할 경우 트랜잭션 A는 같은 쿼리임에도 다른 값을 얻게 된다.

    - 즉, 한 트랜잭션 내에서 같은 쿼리를 두 번 실행할 경우 발생할 수 있는 데이터 불일치이며, Dirty Read에 비해 발생할 확률이 낮다.

     

     

    ▶ Phantom Read (허상 읽기)

    - 트랜잭션 A가 어떤 조건으로 테이블의 특정 범위 값들[1, 2, 3, 4, 5]을 읽었다. 이후 A는 같은 쿼리를 실행할 예정인데 중간에 트랜잭션 B가 같은 테이블에 값들[6, 7, 8]을 추가해버려서 A가 같은 쿼리를 날렸을 때 두 쿼리의 값이 달라진다.

    - 즉, 한 트랜잭션 내에서 일정 범위의 레코드를 조회하는 쿼리를 두 번 실행할 경우 발생할 수 있는 데이터 불일치이다.

     

     

    ▶ Lost Updates (소실된 수정)

    - 트랜잭션 A와 트랜잭션 B가 같은 값을 수정하려고 합니다. 트랜잭션 A가 먼저 값 1을 2로 변경하였고 그 다음 트랜잭션 B가 1을 3으로 변경하였다. A와 B 둘 다 정상적으로 값을 변경하고 커밋하였지만, 나중에 커밋한 B의 값이 A가 변경한 값을 덮어쓰기 하여 값 2가 소실되었다. 

    - 즉, 다수의 트랜잭션이 같은 레코드를 변경할 경우 발생할 수 있는 데이터 소실이다.

     

     

     

     

     

    위와 같은 상황(다수의 트랜잭션의 경쟁)을 방지하기 위해 격리 수준을 설정할 수 있습니다.

     

    @Transactional 어노테이션의 isolation 속성으로 설정할 수 있고 스프링에는 다음 다섯 가지의 격리 수준이 정의되어 있습니다.

     

     

     

     

    ▶ DEFAULT (default)

    - 사용하는 DB의 기본 Isolation Level을 사용

     

     

    ▶ READ_UNCOMMITED (level 0)

    - 커밋되지 않은 값에 대한 읽기를 허용

    - 특정 트랜잭션이 어떤 값을 변경했지만 커밋하지 않은 경우에도 다른 트랜잭션들은 그 값을 읽을 수 있다.

    - Dirty Read, Non-Repeatable Read, Phantom Read 발생

     

     

    ▶ READ_COMMITED (level 1) : Oracle, H2 default isolation level

    - 커밋된 값에 대해서만 읽기를 허용

    - 특정 트랜잭션이 어떤 값을 변경하고 커밋하기 전 까지 다른 트랜잭션들은 해당 값에 접근할 수 없다.

    - Dirty Read 방지

    - Non-Repeatable Read, Phantom Read 발생

     

     

    ▶ REPEATABLE_READ (level 2) : MySQL default isolation level

    - 트랜잭션이 어떤 값을 여러 번 읽어도 동일한 값을 읽도록 보장

    - 특정 트랜잭션이 어떤 값을 읽은 후 같은 쿼리를 한번 더 실행할 때 현재 DB의 값이 아니라 앞에서 수행한 DB snapshot을 읽어옴. 즉, 동일 트랜잭션 내에서 같은 쿼리에 대한 일관성을 보장

    - 해당 레코드에 Lock이 걸리지 않아서 수행중인 트랜잭션이 끝나지 않아도 다른 트랜잭션이 해당 레코드를 읽기 및 쓰기할 수 있음.

    - 예외로 테이블에 명시적인 Locking Read를 할 경우 Gap Lock이 설정되기 때문에 다른 트랜잭션에서 쓰기를 할 수 없음 -> Phantom Read 방지

    Dirty Read, Non-Repeatable Read 방지

    - Phantom Read 발생

     

     

    ▶ SERIALIZABLE (level 3)

    - 트랜잭션이 테이블을 여러 번 읽어도 동일한 값을 읽도록 보장

    - 트랜잭션이 지속되는 동안 SELECT문에 사용되는 모든 테이블에 Shared Lock이 발생하기 때문에 다른 트랜잭션이 테이블에 삽입, 수정, 삭제를 할 수 없다.

    - 모든 동시성 문제를 해결할 수 있지만 성능이 현저히 떨어지기 때문에 거의 사용하지 않는다.

    - Dirty Read, Non-Repeatable Read, Phantom Read 방지

     

     

     

     

     

     

     

     

     

    끝.

    반응형
Designed by Tistory.