[DB] 5. 트랜잭션(Transaction)

2024. 12. 15. 16:54CS/DB

트랜잭션(transaction)은 데이터베이스의 상태를 변경시키는 하나의 논리적 작업 단위를 의미한다.

1. ACID

트랜잭션은 4가지 성질을 충족해야 한다.

1. 원자성 (Atomicity): 트랜잭션의 모든 연산(액션, action)이 수행되거나 수행되지 않아야 하는 성질

2. 일관성 (Consistency)

3. 고립성 (Isolation): 여러 트랜잭션의 교차 수행 결과와 직렬 수행 결과가 동일해야 하는 성질

4. 지속성 (Durability)

2. 스케줄(Schedule)

- DBMS에서 트랜잭션은 일련의 작업(액션, action)들로 구성되어 있다. 트랜잭션의 작업에는 읽기(read), 쓰기(write), 완료(commit), 철회(abort)가 있다. 

- 스케줄은 (여러) 트랜잭션 실행 시 진행되는 작업들의 순서를 의미한다. 쉽게 말해, 액션들의 실행 순서(execution sequence)를 나타낸 것이다.

 

- 완전 스케줄(complete schedule)은 각 트랜잭션에 대해 철회 또는 완료 연산이 포함되어 있는 스케줄이다. 즉, 트랜잭션의 모든 연산이 포함되어 있는 스케줄이다.

- 직렬 스케줄(serial schedule)은 각 트랜잭션들이 하나씩 순차적으로 수행되는 스케줄을 의미힌다.

직렬 스케줄

 

2.1. 교차 수행(Interleaved Execution)

DBMS는 성능 향상(비용 절감)을 위해 여러 트랜잭션을 교차적으로 수행한다.

 

교차 수행의 필요성:

1. 트랜잭션 T1이 디스크에서 페이지를 읽어올 때(I/O 연산), CPU는 트랜잭션 T2를 처리(CPU 연산)할 수 있다. 이러면 I/O 연산과 CPU 연산이 병행하여, 시간당 시스템 처리량(throughput)을 높여준다.

2. 긴 트랜잭션 이후에 짧은 트랜잭션이 실행되어도 교차 수행을 하기 때문에 긴 트랜잭션으로 인한 병목이 줄어든다.

 

하지만 안타깝게도, DBMS는 모든 교차 수행을 허용하지 않는다.

서로 다른 트랜잭션에 속한 두 작업이 동일한 데이터 객체를 사용할 때, 적어도 하나가 쓰기 작업이라면 "해당 객체에 대해 두 작업이 충돌(conflict)났다"라고 표현한다.

 

충돌 상황: 

1. WR 충돌(미완료 데이터 읽음): 미완료 트랜잭션이 수정한 데이터 객체를 읽는 경우

2. RW 충돌(비반복 읽기, unrepeatable read): 미완료 트랜잭션이 읽은 데이터 객체를 수정한 경우

3. WW 충돌(미완료 데이터 겹쳐 쓰기): 미완료 트랜잭션이 수정한 데이터 객체를 수정한 경우, 이 경우에는 갱신 분실(loss update)가 일어난다.

 

- 직렬 가능한 스케줄(serializable schedule)은 트랜잭션들을 직렬 순서로 실행했을 때의 결과와 동일한 결과를 보이는 스케줄을 의미한다. 이때, 모든 직렬 순서와 동일한 결과를 보장하지 않아도 된다. 하지만, 적어도 하나와 동일해야 한다.  

+ 의미 확장(철회된 트랜잭선 포함): 해당 스케줄에서 완료된(commited) 트랜잭션으로만 구성된 직렬 스케줄과 동일한 영향을 주는 스케줄을 의미한다. 즉, 철회된(absorted) 트랜잭션은 완전히 undo해야 한다. 

- 연쇄 철회(cascading absorts): 트랜잭션을 철회할 때, 해당 트랜잭션에서 수정한 객체를 읽은 트래잭션도 철회해야 하며, 해당 과정은 연쇄적으로 일어나다.

 

하지만, 일부 상황에서는 완전한 undo가 불가능하다. 대표적으로, 특정 트랜잭션을 undo할 시 해당 트랜잭션에서 수정한 객체가 이후 트랜잭션에서 갱신되어도 해당 결과가 없어진다.

완전한 undo가 불가한 경우 이를 복구 불가능(unrecoverable)하다 라고 한다. 복구 가능 스케줄(recoverable schedule)의 트랜잭션은 자신이 읽은 객체를 수정한 다른 트랜잭션이 모두 완료(commit)될 때까지 완료(commit)하지 않는다. 변경 사항이 완료된(commited) 객체만 읽을 경우, 해당 스케줄은 복구 가능할 뿐만 아니라 연쇄 철회까지 방지한다.

3. 동시성 제어(Concurrency Control)

DBMS는 직렬 가능하고 복구 가능한 스케줄만 허용해야 한다. 이를 위해 잠금 프로토콜(locking protocal)을 사용한다.

 

3.1. 엄격한 2단계 잠금(Strict Two-Phase Locking) 

해당 프로토콜은 가장 널리 사용되는 잠금 프로토콜이며, 두 가지 규칙을 가지고 있다: 

1. 트랜잭션이 데이터 객체의 읽기(쓰기) 작업 수행하기 위해서는, 먼저 해당 객체에 대한 공용(전용) 잠금 권한을 갖고 있어야 한다.

2. 트랜잭션이 완료(commit)되면 소유한 모든 잠금을 반환해야 한다.

 

해당 규칙은 트랜잭션의 안전한 교차 수행을 보장한다. 즉, 해당 프로토콜은 직렬 가능한 스케줄만 허용한다.

 

3.2. 교착상태(Deadlock)

트랜잭션 T1이 객체 A에 대한 전용 잠금 권한을 가지고 있고, 트랜잭션 T2가 객체 B에 대한 전용 잠금 권한을 가지고 있는 상황에서, T1이 객체 B의 전용 잠금을 요구하고, T2는 객체 A의 전용 잠금을 요구하게 되면 무한 대기 상태인 교착상태에 빠진다.

 

교착 상태 해결방법은 교착상태를 검출하는 것이고, 이는 타임아웃(timeout) 기법으로 간단히 검출 가능하다. 즉, 트랜잭션이 일정시간 이상 대기(blcoking)하면 이를 교착상태로 간주하고 해당 트랜잭션을 철회(absorting)한다.

 

3.3. 잠금 오버헤드

소수의 트랜잭션끼리는 충돌할 확률이 낮기 때문에, 초기에는 트랜잭션 수가 증가할수록 처리량도 함께 증가한다.

그러나 트랜잭션 수가 많아지면서 동일한 데이터 객체에 트랜잭션이 점점 집중되어 대기 시간이 늘어나고, 결국 특정 지점에서 처리량이 감소하게 된다.

이때, "해당 지점에서 스래슁(thrashing)이 일어난다"라고 표현한다.

스래슁이 발생하면 DBMS 관리자는 트랜잭션 수를 줄여야 한다.

일반적으로, 해당 상황을 예방하기 위해 가능한 작은 크기의 객체에 잠금을 설정하는 방식을 사용한다.

 

3.4. 잠금 단위(Granularity)

DBMS는 전체 테이블 단위 혹은 행 단위로 잠금을 할 수 있다. 잠금 단위 즉, 잠금 대상이 되는 객체 크기를 조절할 수 있다.

예시 1). 이때, 선택 조건을 만족하는 행들을 수정하는 트랜잭션은 전체 테이블에 공용 잠금을 설정하고, 수정하는 행들은 전용 잠금으로 설정하는 것이 좋은 선택이 될 수 있다.

예시 2). 만약, 조건을 만족하는 행들을 읽는 트랜잭션은 어떤 잠금 단위가 좋을까? 조건을 만족하는 행만 공용 잠금으로 설정하는 제안은 적절할까? 해당 제안은 조건을 만족하는 새로운 행의 삽입을 고려하지 않은 제안이다. 즉, 팬텀(phantom) 문제를 고려하지 않은 제안이다.

팬텀 문제는 트랜잭션 내에서 동일한 작업을 반복 실행했을 때 결과가 달라지는 현상을 뜻한다. 이는 반복 실행 사이에 다른 트랜잭션이 데이터를 삽입 혹은 삭제하여 발생한다. 그렇기 때문에 해당 예시에는 낮은 동시성이 따르겠지만, 전체 테이블을 잠금하는 것이 안전하다.

 

3.5. 잠금 오버헤드 제어

SQL은 트랜잭션에 의해 초래되는 잠금 오버헤드를 제어할 수 있게 몇 가지 선택지를 제시해준다. 대표적으로, 접근 모드(access model)과 고립 수준(isolation) 선택지가 있다.

 

접근 모드(access mode): READ ONLY, READ WRITE

고립 수준(isolation level):  트랜잭션끼리 얼마나 서로 고립되어 있는지를 나타내는 정도

1. SERIALIZABLE: 변경 사항이 완료된(commited) 객체만 읽는다. 읽고 쓰는 어떠한 값도, 완료될 때까지 다른 트랜잭션에 의해 변경되지 않는다. 테이블 전체를 공용 잠금해 팬텀 문제를 방지한다.

2. REPEATABLE READ: 변경 사항이 완료된(commited) 값만 읽는다. 읽고 쓰는 어떠한 값도, 완료될 때까지 다른 트랜잭션에 의해 변경되지 않는다. 조건을 만족하는 행만 공용 잠금해 팬텀 문제가 생길 수 있다.

3. READ COMMITED: 변경 사항이 완료된(commited) 값만 읽지만 완료(commited)되기 전에 다른 트랜잭션에 의해 수정될 수 있다. 반면, 쓰는 값은 완료될 때까지 다른 트랜잭션에 의해 변경되지 않는다. (읽기 전에 공용 잠금을 획득하고, 획득한 즉시 해제한다.)

4. READ UNCOMMITED: 변경 사항이 진행 중인 값도 읽는다. (읽기 전에 공용 잠금을 획득하지 않는다. READ ONLY만 가능)

4. 장애 복구(Crash Recovery)

DBMS는 복구 관리자(recovery manager)는 원자성과 지속성을 보장할 책임이 있다.

시스템 장애 시 미완료 트랜잭션은 undo함으로써 원자성을 보장하고, 완료된 트랜잭션은 redo해 지속성을 보장해야 한다.

 

4.1. steal & force

객체 저장에 관한 두 가지 질문에 대해 살펴보자:

- steal 관점: 객체의 수정 사항은 트랜잭션이 완료되기 전에 디스크에 기록되는가?

- force 관점: 트랜잭션이 완료되는 즉시 객체의 수정 사항이 디스크에 반영 되는가?

 

복구 관리자 입장에서는 no-steal 접근과 force 접근이 가장 편하다.

철회된 트랜잭션은 no-steal로 인해 undo할 필요가 없고, 완료된 트랜잭션은 디스크에 바로 기록되기 때문에 redo할 필요가 없기 때문이다.

그러나 이 접근법에는 여러 문제가 존재한다.

no-steal 문제점: 트랜잭션은 완료될 때까지 수정한 모든 페이지는 버퍼 풀에 머물러야 하는데, 이는 크기가 큰 트랜잭션에게 매우 치명적이다.

force 문제점: 트랜잭션이 완료될 때마다 디스크에 수정 사항을 반영해야 하는데, 여러 트랜잭션이 연속적으로 동일 페이지를 수정하면 과도한 I/O 비용이 발생할 수 있다.

이런 이유로, 대부분의 DBMS는 steal, no-force 접근법을 사용한다.

더티한 프레임이 교체 대상으로 선정되면, 수행 중이더라도 수정 사항을 디스크에 기록하고, 트랜잭션이 완료되도 수정 사항을 디스크에 강제로 저장하지 않는다.

 

4.2. 정상 실행 시 복구를 위한 작업

정상 실행 시 복구에 필요한 정보를 기록하고 유지해야 한다.

DB의 모든 수정 사항 로그는 안정 저장소(Stable Storage)에 저장된다. 

DB 변경을 기술하는 로그는 DB 변경이 이루어지기 전에 안정 저장소에 기록된다.

 

로그는 철회되거나 완료되지 않은 트랜잭션은 undo하고, 완료했지만 디스크에 반영되지 않은 트랜잭션은 redo할 수 있게 해준다. 이때, 완료되지 않은 트랜잭션 중 steal 접근으로 디스크에 수정 사항이 반영된 트랜잭션을 식별하고 undo해야 한다.

 

4.3.  ARIES

ARIES는 steal, no-force 접근법을 채택한 시스템에서 복구를 수행할 수 있게 도와주는 복구 알고리즘이다.

1. 분석 단계: 더티 페이지와 장애 시점에 수행 중인 트랜잭션을 식별한다.

2. Redo 단계: 저장된 로그들로 재실행할 지점을 찾은 후, 해당 지점부터 이후의 모든 작업을 다시 실행하여, DB를 장애 발생 직전 상태로 복구한다.

3. Undo 단계: 완료된 트랜잭션만 반영하기 위해, 완료되지 않은 트랜잭션은 undo한다.

 

'CS > DB' 카테고리의 다른 글

[DB] 4. 외부 정렬(External Sorting)  (0) 2024.12.14
[DB] 3. 질의 수행(Query Evaluation)  (2) 2024.12.14
[DB] 2. 디스크 & 파일(Disk & File)  (0) 2024.12.12
[DB] 1. 인덱싱(Indexing)  (0) 2024.08.29