Post

Database - 트랜잭션

Database - Transaction

사실 이전에 각 DBMS에서 트랜잭션을 어떻게 걸고 어떻게 커밋하고 어떻게 롤백하는지에 대해서는 언급한적이 있지만 실질적으로 Transaction이 무엇인지에 대해서 언급한적은 없는거 같다.
그래서 오늘은 트랜잭션이 무엇인지에 대해서 포스팅 해보려 한다.

1. 개요

트랜잭션이란 DBMS에서 구동되는 명령어들의 최소 유닛인데, 한 개의 명령어 혹은 다수의 명령어가 순차적으로 실행된다. 이 명령어 묶음은 더 쪼갤 수 없으며 묶음이 전체 다 실행되는게 아니라면 실행되기 전으로 되돌려야(Rollback)한다.

이는 동시 실행하는 경우에 대해 데이터 안정성을 보장하기 위한 방식으로 레이스 컨디션이나 하드웨어 혹은 OS에서 발생하는 문제로 데이터가 소실되거나 덜 변경된 상태를 피하기 위해 필요하다.

2. 트랜잭션이 가져야할 필수 요소

데이터가 정확하냐에 대한 내용으로 이 Correctness를 유지하기 위해서는 아래 4요소를 만족해야한다.

1) Atomicity

원자성이라고 해석할 수 있으며 실행 혹은 취소(Rollback) 특성은 이 원자성때문에 생기는 것이다.
이 원자성이 보장되지 않는다면 하고자하는 데이터의 작업에 다른 작업이 끼어들게 되고 이로 인해 데이터의 정합성이 맞지 않게 된다.

이러한 Atomicity를 유지하기 위한 방법은 아래의 두 가지가 있다.

ㄱ. Logging

모든 작업에 대해서 기록해두는 것이다. 어떻게 작업했는지 기록이 남아있다면 그 기록을 읽어 반대로 진행하면 된다.

ㄴ. Shadow Paging

페이지를 복사해두고 복사본에 작업한 뒤에 트랜잭션이 커밋되면 해당 페이지를 Visible하게 처리한다.

2) Consistency

일관성이라고 해석 될 수 있다. 미리 정의된 규칙에서만 수정이 가능한 특성을 말하는 것으로 숫자 타입에 문자열로 저장이 안되는 게 하는 것을 예로 들수있다.

3) Isolation

고립 혹은 격리라고 해석 할 수 있다. 한 개의 트랜잭션은 다른 트랜잭션으로부터 격리되어 실행된다.

이 격리를 구현하는 방식은 총 두 가지 카테고리로 프로토콜이 있다.

ㄱ. Pessimistic

아예 처음부터 격리 문제가 발생하지 않게끔 하는 것이다. 이를 위해서 lock을 통해 제어하며 대부분의 Database가 차용한 방식이다.

ㄴ. Optimistic

충돌이 드물다고 가정하고 그냥 작업하되 어떤 작업 후에 검증간 Conflict가 발견되면 이후에 fix하는 방식이다.

ㄷ. 직렬화(Serializability)

동시 트랜잭션과 직렬 실행 결과가 결과적으로 같게끔 스케줄링이 필요한데 이를 직렬화라고 한다.
이 스케줄링을 잘못하면 결과값에 문제가 생기는데 이 문제를 충돌이라고 하며 서로 다른 트랜잭션이 동일한 객체에 연산을 할때 그 중 하나가 쓰기일때 발생한다. 충돌을 야기시킬 수 있는 대표적인 예시는 아래와 같다.

  • Read-Write Conflicts (R-W, Unrepeatable Read)
    img.png
    트랜잭션 1에서 어떤 A 객체의 값을 연속해서 읽어야할 때 트랜잭션 2에서 A 값을 변경해버리는 경우 트랜잭션 1에서 다시 A를 읽었을 때 이전과 동일한 값이 아닌 트랜잭션 2에서 변경한 값을 읽어오게 된다.

  • Write-Read Conflicts (W-R, Dirty Read)
    img_1.png
    트랜잭션 1에서 A값을 변경했고, 트랜잭션 2에서 A값을 읽어서 변경했는데, 트랜잭션 1이 롤백해버리는 경우이다. 트랜잭션 2에서 읽어올때 트랜잭션 1에서 처리한 이전 값을 기반으로 더한 값이라면 처리하기 애매해진다.

  • Write-Write Conflicts (W-W, Lost Update)
    img_2.png
    트랜잭션 1에서 A값에 대해 쓰고, B 값에 대해서 쓰는데 그 사이에 트랜잭션 2가 끼어들어서 A와 B에 대해서 수정을 가하게된다면 트랜잭션 1,2 에서 의도한 값이 섞여서 반영되어 버린다.

위와 같은 문제를 해결하기 위해서는 충돌 직렬 가능성(Conflict Serializability)를 확인하면 되는데 충돌 관계를 나타낸 의존성 그래프가 사이클을 갖지 않는지 확인하면 순차적 실행으로 변경이 가능하다.

※ 진짜 이런 일이 많이 일어나는가?
트랜잭션의 예시를 들 때 흔히들 많이 드는 예시는 은행 예금 잔고 처리에 대한 것이지만, 사실 개인적인 경험으로는 생각보다 꽤 많이 일어난다. 이전에 내가 개발했었던 프로젝트 관리 솔루션의 경우 특정 Task에 대한 상태 변화를 해당 Task의 담당자로 지정된 인원이 수정할 수 있게 되어있었는데, Task의 담당자가 1명일때는 상관없었지만 다수일 때 W-W이나 W-R, R-W 충돌이 나서 이를 해결할 방법을 찾느라 골치아팠던 기억이 있다.

ㄹ. 의존성 그래프를 이용한 충돌 직렬 가능성

트랙잭션을 Node으로 표현한다. 그리고 Node간에 연결된 간선 O1은 수정 대상이 되는 객체를 나타내는 것이다. 만약 Node T1에서 T2로 간다면 T1에서 O1을 사용하고 이후 T2에서 O1을 사용한다는 뜻이다.
아래의 그림을 보자

img.png

T1에서 A에 쓰기를 하고 T2에서 A에 쓰기를 한 뒤, T2에서 B에 쓰기를 하고 T2에서 B에 쓰기를 한다.
위와 같은 것을 의존성 그래프로 나타내면 아래와 같다.

img_1.png

보는 것과 같이 사이클이 생기는 것을 알 수 있으며 위와 같은 형태로 사이클이 생긴다는 것은 직렬화 할 수 없음을 말한다.

즉 아래와 같이 표현할 수 있다.

img_3.png

만약 의존성 그래프를 그렸을 때 사이클이 생기지 않는다면 위와 같이 각 트랜잭션 내의 작업을 연속적으로 배치해도 동일한 결과를 보인다.

4) Durability

내구성 정도로 해석할 수 있으며 어떤 트랜잭션이 commit된다면 해당 트랜잭션의 결과는 중간에 다른 트랜잭션에 의해 변경되지 않는한 지속되는 것을 말한다. 영속성이라고 표현할 수도 있겠다.

※ 추가 업데이트 및 검증 예정이고, 아직 완벽하지 않으니 참고만 바란다.

참고자료

  • 서강대학교 김영재 교수님 고급데이터 베이스 강의 자료
This post is licensed under CC BY 4.0 by the author.