Skip to content

3. 트랜잭션(Transaction)과 ACID


1. 키워드

  • 트랜잭션(Transaction)
  • ACID(Atomicity, Consistency, Isolation, Durability)


2. 트랜잭션이란

  • 데이터베이스에서 트랜잭션이란, 데이터의 추가, 수정, 삭제 등을 처리하는 여러 단계들을 하나로 묶은 논리적인 작업 단위를 의미한다.
  • 참고로 DBMS의 성능은 초당 트랜잭션의 실행 수(TPS: Transaction Per Second)로 측정한다.
  • 은행 계좌 이체를 예로 들면, 송신자가 일정 금액을 보내면 송신자 계좌의 금액은 감소되어야 하고, 수신자의 계좌의 금액은 증가되어야 하는데 이 과정이 모두 하나의 트랜잭션 단위로 이루어져야 할 것이다.
  • 만약 해당 작업을 진행하는 도중, 어느 한 단계에서라도 문제가 발생한다면 위 과정 자체가 모두 취소되고 원 상태로 복구(Rollback)되어야 한다.


3. ACID란

  • 위와 같이 정보를 저장하고 처리하는 데이터베이스의 특성을 고려하여, 데이터베이스는 기본적으로 다음과 같은 ACID라는 원칙을 지켜야 한다.


1) 원자성(Atomicity)

  • 원자성이란, 트랜잭션이 분해가 불가능한 최소의 단위인 하나의 원자처럼 동작한다는 의미인다.
  • 트랜잭션 내의 모든 연산들은 반드시 한꺼번에 완전하게 전체가 정상적으로 수행이 완료되거나 아니면 어떠한 연산도 수행되지 않아야 한다(All or Nothing).
  • 예를 들어, 은행 계좌 이체는 성공할 수도 있고 실패하는 2가지 경우만 존재하는데, 송신자의 잔고가 감소하는 작업만 성공하고, 수신자의 잔고가 증가하는 작업은 실패해서는 안 된다.
  • 즉, 원자성은 이와 같이 중간 단계까지만 실행되고 실패하는 일이 없도록 보장해야 한다는 것을 의미한다.


2) 일관성(Consistency)

  • 트랜잭션의 실행이 성공적으로 완료되면 데이터베이스를 항상 일관성있는 상태로 유지해야 된다라는 것을 의미한다.
  • 예를 들어, 고객 정보가 담겨 있는 데이터베이스에 새로운 고객이 등록될 경우, 그 데이터베이스를 참조하는 다른 하위 계층의 데이터베이스 또는 테이블도 같은 고객의 세부 정보(이름, 전화번호 등)를 가져와야 한다.
  • 또다른 예시로, 모든 계좌는 잔고가 있어야 한다라는 무결성 제약이 있다면 이를 위반하는 트랜잭션은 모두 중단되어야 한다.


무결성(Integrity)

  • 데이터베이스에 저장된 데이터 값과 그것이 표현하는 현실 세계의 실제 값이 일치해야 한다라는 정확성을 가지는 조건을 의미한다.
  • 모든 계좌는 잔고가 있어야 한다는 무결성 제약 조건의 경우 0을 포함한 어떠한 형태의 값이라도 기록되어야 하는 Null 무결성을 설명하고 있다.
  • 실제 계좌의 잔고는 0 혹은 마이너스가 기록될 수 있을지라도 아무 값이 없는 Null 상태가 될 경우 다른 계좌와의 정상적인 트랜잭션이 일어날 수 없기 때문이다.


3) 독립성 또는 고립성(Isolation)

  • 트랜잭션이 수행되고 있을 때, 다른 트랜잭션의 연산 작업이 중간에 끼어들어 기존 트랜잭션의 작업에 영향을 주지 못 하도록 독립성을 보장하는 것을 의미한다.
  • 데이터베이스의 트랜잭션도 CPU의 프로세스들을 처리하는 것과 마찬가지로, 성능과 효율성을 위해 병행 처리를 할 수 있다.
  • 병행 처리란 특정 시간 동안 작업을 하다가 부여된 시간이 끝나면 다른 트랜잭션을 실행해 나가는 방식을 의미한다.
  • 이때, 많은 트랜잭션들이 병행되어 조금씩 처리되어 가는 도중에 공통된 데이터를 조작하는 경우, 데이터가 흔히 이야기하는 "꼬이는" 상황이 발생할 수 있다.
  • 예를 들어, A 트랜잭션에서 은행 계좌의 100원을 빼내는 연산을 하던 도중에 B 트랜잭션으로 처리 순서가 넘어가서 50원을 빼내는 연산이 먼저 처리되는 경우 등이 있다.
  • 따라서 독립성 또는 고립성을 보장하면, 은행 직원이 계좌의 이체 작업을 하는 도중 다른 추가 데이터베이스 쿼리(추가, 수정, 삭제 등)를 실행하더라도 그 순간에 해당 계좌들을 들여다보거나 다른 작업을 동시에 수행할 수 없게 된다.
  • 즉, 기존 트랜잭션이 완료되고 나서야 계좌를 확인하거나 추가 쿼리를 실행할 수 있도록 트랜잭션 처리는 순차적이어야 한다.


4) 지속성(Durability)

  • 성공적으로 수행된 트랜잭션은 요청된 작업의 내용이 데이터베이스에 영원히 반영되어야 함을 의미한다.
  • 시스템 문제가 발생하거나, 데이터베이스의 일치 작업을 수행하더라도 데이터베이스의 내용은 기존과 같이 유지되어야 함을 의미한다.
  • 이를 위해서 모든 트랜잭션은 로그로 남겨져 시스템 장애 발생 전 상태로 되돌릴 수 있어야 한다.
  • 또한, 모든 트랜잭션은 로그에 모든 것이 저장된 후에만 정상적으로 처리된 Commit 상태로 고려될 수 있다.


4. ACID의 구현

1) 원자성(Atomicity) 보장

  • 원자성을 보장하기 위해서는 처리 중인 트랜잭션에서 오류가 발생했을 때 현재의 처리 중인 내용을 취소하고 트랜잭션 실행 시 임시 영역에 저장해 두었던 이전의 상태(Commit)를 다시 불러와서 복구(Rollback)하는 방법이 있다.
  • 이전의 데이터들이 임시로 저장되는 별도의 영역을 Rollback Segment라고 한다.
  • 만약 트랜잭션이 처리해야 할 양이 많이지는 경우에는 중간 부분의 Save Point를 지정하여 해당 부분부터만 복구 작업을 수행함으로써 리소스의 낭비를 방지하고 처리 시간을 단축할 수 있따.


2) 일관성(Consistency) 보장

  • 트랜잭션이 일어났을 때 미리 정의된 트리거(Trigger)를 통해 일관성이 보장될 수 있다.
  • 예를 들어, 한 쪽 데이터베이스의 테이블에 정보의 수정이 일어났을 경우 다른 쪽 테이블에도 함께 수정될 수 있도록 명시적으로 자동 업데이트를 하는 명령 등을 구성하는 방법이 있다.


3) 독립성 또는 고립성(Isolation) 보장

  • 독립성 또는 고립성을 보장하는 방식은 다음과 같다.


(1) Lock & Unlock

  • 데이터를 읽거나 쓰기 작업 중일 때는 해당 영역에 Lock을 걸어서 다른 트랜잭션이 접근하지 못 하도록 하고, 먼저 들어온 트랜잭션 요청이 끝나면 Unlock하여 다른 트랜잭션이 처리될 수 있도록 허용하는 방식을 통해 독립성 또는 고립성을 보장할 수 있다.
  • 단, Lock과 Unlock을 잘못 사용하게 되는 경우 어떤 트랜잭션도 수행될 수 없는 데드락(Deadlock) 상태에 빠질 수 있다.


두 가지 Lock 방식

  • 다음과 같이 두 가지의 Lock 방식이 있다.


1] 보수적인 Locking(Conservative Locking)

  • 트랜잭션이 시작되면 모두 Lock을 하는 방식으로 데드락이 발생하는 경우는 없으나 병행성이 떨어지게 된다.


2] 강한 Locking(Strict Locking)

  • 트랜잭션이 완전히 Commit될 때까지 Lock을 걸어두고 있다가 Commit 이후 Unlock을 하는 방식으로, 데드락이 발생할 수 있지만 병행성이 좋다.
  • 일반적으로 병행성이 좋은 Strict Locking 방식을 사용한다.


(2) 2단계 Locking 프로토콜(2PL: 2 Phase Locking Protocol)

  • 데드락을 방지하기 위한 2단계 Locking 프로토콜은, 여러 트랜잭션이 동시에 한 데이터로 접근할 수 없도록 2가지의 Lock 방식으로 트랜잭션 처리에 제한을 두는 규약을 의미한다.
  • Growing Phase(상승 단계)에서는 Lock만 수행되어야 하고 Shrinking Phase(하강 단계)에서는 Unlock만 수행되어 Lock과 Unlock이 교차로 수행되지 않도록 하는 방식을 의미한다.
  • 특히 네트워크 환경에서는 그 환경의 특성상 ACID 특성을 보장하는 것이 매우 어려우므로 이와 같은 2단계 Locking 프로토콜 방식을 통해 ACID 특성을 보장할 수 있다.


(3) 다중 버전 동시성 제어(MVCC: Multi-Version Concurrency Control)

  • MVCC 모델에서는 Snapshot을 이용하는 방법으로 동시성을 제어하여 데드락을 방지하고 개선된 트랜잭션 성능을 보장한다.
  • 먼저, 트랜잭션이 일어나는 특정 시점의 데이터베이스 Snapshot을 읽는다.
  • 이 Snapshot 데이터에 대한 트랜잭션 수행 중 발생하는 변경 사항은 트랜잭션이 Commit될 때까지 다른 사용자가 볼 수 없다.
  • 데이터가 업데이트 되면, 이전의 데이터를 덮어쓰는 게 아니라 새로운 버전의 데이터를 만든다.
  • 대신 이전 버전의 데이터와 비교해서 변경된 내용을 기록한다.
  • 위와 같은 과정을 거쳐 하나의 데이터에 대해 여러 버전의 데이터가 존재하게 된다.
  • 사용자는 가장 마지막 버전의 데이터를 읽게 된다.
  • MVCC의 접근 방식은 Snapshot을 이용하여 Locking을 필요로 하지 않기 때문에 일반적인 RDBMS보다 더 빠르게 동작할 수 있다.
  • 또한 데이터를 읽기 시작할 때, 다른 사람이 그 데이터를 삭제하거나 수정하더라도 영향을 받지 않고 데이터를 사용할 수 있다.
  • 단, MVCC 방식을 이용하게 될 경우 사용하지 않는 데이터가 계속 쌓이게 되므로 데이터를 적절하게 정리해 주는 별도의 시스템이 필요하다.
  • 또한, 데이터의 버전이 충돌하면 애플리케이션 영역에서 이러한 문제를 해결해야 한다.

References