지난 글에 이어서 이번에는 트랜잭션의 회복 과정에 대해 자세히 알아보겠습니다.


트랜잭션의 회복(Recovery)과 로그(Log), 로그 레코드


회복은 데이터베이스를 장애발생 이전의 일관된 상태로 복원시키는 것입니다.

여기서 일관된 상태란 DB에 오류와 모순이 없는 상태를 뜻하며,

이를 일으키는 장애의 유형에는 트랜잭션 장애(논리적 오류), 시스템 장애(하드웨어 오류), 미디어 장애(디스크 헤드 고장)가 있습니다.

 

회복 자체가 DB에 꼭 필요한 기능이기 때문에,

회복 관리자(Recovery Manager)는 DBMS 전체 코드의 10% 이상을 차지할 정도로 비중이 큽니다.

이런 회복의 기본 원리는 중복(redundancy)이며, 말 그대로 '복제' 혹은 '기록'하는 방식이 됩니다.

덤프(dump)는 지금의 데이터를 다른 저장장치로 모든 걸 복제하는 full backup이고,

로그(log) 데이터의 옛 값과 새 값을 별도의 파일에 기록을 하는 것입니다.

 

회복을 위한 조치로는 REDO, UNDO가 있는데, 맨 앞에서 지나가듯 말했죠?

보통 REDO는 DB의 내용이 손상된 경우 가장 최근의 복제본을 만들고,

 복제본 이후에 일어난 변경을 로그에 저장한 뒤 똑같이 재실행하면서 DB를 복원하는 것이며,

UNDO는 DB의 내용 자체는 손상되지 않았지만, 변경중 혹은 변경된 내용에 대한 신뢰성을 잃어버렸을 때

 변경된 과정을 담은 로그를 이용 거꾸로 스텝을 밟아가며 모든 변경들을 취소하여 원래의 DB를 복원합니다.


그렇다면 이 로그라는 건 어떻게 생겼길래 이걸 보고 Redo, Undo를 하는 걸까요?

로그 레코드를 보고 해당 로그가 트랜잭션의 어떤 과정을 담았는지 볼 수 있습니다.

<T, Start> : 트랜잭션 T가 실행 시작
<T, X, V1, V2> : 트랜잭션 T가 아이템 X의 값을 V1에서 V2로 변경
<T, Commit> : 트랜잭션 T가 실행 완료

 로그 레코드 안정 저장소에 기록하는 식으로 로그가 저장이 됩니다.

 

일련의 트랜잭션 과정(로그)을 모두 저장소에 담으면 로그가 차지하는 용량이 꽤 커지겠죠?

로그와 데이터가 차지하는 용량을 줄이기 위해 DB는 이를 저장하고 삭제하는 기준을 갖고 있습니다.

1. 실패한 트랜잭션의 로그는 불필요
    → 이미 rollback 되었다면 로그는 필요하지 않음
2. 성공한 트랜잭션의 갱신 전 데이터는 불필요
    → REDO를 위해서는 새로운, 최신의 값만 필요
3. 하나의 데이터가 여러 트랜잭션에 의해 여러 번 갱신되었을 때, 마지막 값만 필요
    → REDO시 중간 과정의 데이터 값은 불필요

즉시 갱신의 회복


미완료 갱신(uncommitted update, 즉시 갱신)이란,

아직 트랜잭션이 완료되지 않았어도 변경된 값이 DB에 반영된 갱신입니다.

 

이 상태에서 트랜잭션이 진행중일 때 트랜잭션 장애 혹은 시스템 붕괴가 일어나면

트랜잭션이 실행되기 전 상태의 데이터로 복원하는 UNDO 프로시저가 사용됩니다.

UNDO 프로시저란 트랜잭션 T가 변경한 모든 데이터 아이템 값을 로그에 기록하고, 기록된 역순으로 변경 전 값으로 환원하는 과정입니다. 해체는 조립의 역순..

 

여기 두 트랜잭션 T1과 T2가 있습니다.

T1은 계좌 A에서 100을 계좌 B로 이체, T2는 계좌 C로부터 200을 인출하는 트랜잭션입니다.

<T1, T2> 의 순으로 실행한 로그는 다음과 같습니다.

이 때 이 트랜잭션의 상태를 즉시 갱신이라 하면,

데이터베이스(디스크)에 적용된 출력 순서는 다음과 같습니다.

이 일련의 과정 중에 시스템 붕괴가 발생하면 어떻게 될까요?

우선 장애가 일어나면 회복 관리자는 로그를 검사합니다.

만일 로그에 <T, Start> 레코드만 있고 <T, Commit> 레코드가 없으면 Undo(T)를 수행하고,

로그에 <T, Start> 레코드와 <T, Commit> 레코드가 모두 다 있으면 Redo(T)를 수행합니다.

<T1, Commit> 하기 직전에 장애가 발생하면 Undo(T1)를 수행하고,

<T2, Commit> 하기 직전에 장애가 발생하면 우선 Undo(T2)를 수행한 뒤 Redo(T1)를 수행합니다.

T2가 <T2, Commit> 로그 레코드를 출력한 직후 장애가 발생하면 Redo(T1), Redo(T2)를 수행합니다.

일반적으로 Undo 이후에 Redo를 수행하게 됩니다.


지연 갱신의 회복


지금까지 즉시 갱신의 회복에 관해 알아보았는데, 회복의 다른 방식인 지연 갱신이 존재합니다.

지연 갱신은 모든 데이터베이스의 변경을 우선 로그에 먼저 기록합니다.

기록된 로그 레코드는 안정 저장소에 저장되고, 그 후에 데이터베이스를 갱신하여 완료 상태로 갑니다.

실행 도중 장애가 발생하거나 철회가 되면 로그는 그냥 버려지므로 UNDO 연산은 불필요하고,

로그 레코드는 REDO 연산에만 사용됩니다.

 

다시 앞의 트랜잭션 T1과 T2를 가져오겠습니다.

지연 갱신에서는 디스크의 데이터베이스 출력 순서가 다음과 같아집니다.

이러한 지연 갱신의 과정 도중에 시스템 붕괴가 일어나면 어떻게 될까요?

지연 갱신에서는 <T, Start> 레코드와 <T, Commit> 레코드가 모두 있는 트랜잭션에 대해서만 재실행합니다.

위와 같이 세 가지 시점에서 붕괴가 일어났을 때,

Commit을 하기 전이라면 디스크에 값이 저장되지 않았으므로 아무런 회복조치 없이 로그 레코드를 날려버리면 됩니다.

반면 <T1, Commit> 이후라면 Redo(T1)이 필요하고,

<T2, Commit> 이후라면 Redo(T1) → Redo(T2)가 실행됩니다.

앞서 말했듯 UNDO 연산은 지연 갱신의 회복에서 쓰이지 않습니다.


검사시점 회복


앞서 즉시 갱신 및 지연 갱신의 회복 과정에서 Redo와 Undo를 해야 할 트랜잭션을 결정하려면, 불필요한 Redo 연산이 첨가되거나 로그 전체를 조사해야 하므로 너무 많은 시간이 걸리게 됩니다.

이 때 회복 시간을 줄이기 위해 쓰이는 것이 검사시점 방법입니다.

 

검사시점 방법은 일정 시간 간격으로 검사시점을 설정하는 방법으로, 과정은 다음과 같습니다.

1. 메인 메모리에 있는 모든 로그 레코드를 안정 저장소에 출력
2. 변경된 데이터 버퍼 블록을 모두 디스크로 출력
3. 검사시점 표시 로그 레코드 <Checkpoint L>을 안정 저장소에 출력

출처 : 데이터베이스설계 CH20, Wonik Choi

위와 같이 검사시점이 중간에 설정되었고, 이후 장애가 발생했다고 합니다.

장애가 발생할 때 활동중인 트랜잭션 T3, T5를 Undo 하고, 완료된 트랜잭션 T2, T4를 Redo 합니다.

이 때 T1은 검사시점 이전에 이미 Commit 되었으니 회복작업과 무관합니다.

 

그렇다면 이런 Redo, Undo할 트랜잭션 목록은 어떻게 결정될까요?

검사시점을 설정하면 현재 활동중인 트랜잭션을 Undo-list에 넣습니다.

이후 Start 되는 트랜잭션은 Undo-list에 넣고, Commit되는 트랜잭션은 Redo-list에 옮깁니다.

위의 그림을 보면, 검사시점 설정시 활동중인 트랜잭션 T2, T3이 Undo-list에 삽입됩니다.

이후 로그를 차례로 검색하며 <T4, Start> <T5, Start>를 만나 T4, T5를 Undo-list에 삽입합니다.

계속 로그를 검색하며 <T2, Commit> <T4, Commit>을 만나 T2, T4를 Undo-list에서 지우고 Redo-list에 삽입합니다.

 

Undo-list에 있는 모든 트랜잭션에 대해 로그에 기록된 역순으로 Undo 연산이 수행되며,

그 뒤 Redo-list에 있는 모든 트랜잭션에 대해 로그에 기록된 순서대로 Redo를 수행합니다.

따라서 장애 발생시 Undo, Redo의 순서는 다음과 같습니다.

Undo(T5) → Undo(T3) → Redo(T2) → Redo(T4)

 

이 때, Undo 연산으로 회복하는 과정을 후진 회복(backward recovery),

Redo 연산으로 회복하는 과정을 전진 회복(forward recovery) 이라고 합니다.

회복 작업이 완료될 때까지 시스템은 새로운 트랜잭션을 받아들일 수 없습니다.


트랜잭션을 처음 소개할 때 모식도를 다시 보겠습니다.

장애가 발생하면 회복을 하는데, 옆에 WAL 이라고 써져있죠?

WAL은 Write Ahead Log의 약자로, 로그 우선 기록 규약이라고 합니다.

로그 우선 기록 규약은 다음과 같습니다.

i. 트랜잭션 T는 <T, Commit> 로그 레코드를 안정 저장장치에 출력해야만 완료상태로 들어갈 수 있다.
ii. <T, Commit> 로그 레코드를 출력하려면 우선 T에 관련된 모든 로그 레코드를 안정 저장장치에 출력시켜야 한다.
iii. 데이터베이스 버퍼 블록을 출력시키기 위해서는 먼저 이 버퍼 블록의 데이터와 관련된 모든 로그 레코드가 안정 저장장치에 출력되어야 한다.

회복을 하려면 로그가 필요한데, WAL 방식에서는 트랜잭션이 진행될 때 로그 레코드를 log buffer가 아닌 안정 저장장치인 log file에 작성합니다.

"로그 레코드를 안정 저장장치에 기록"은 앞서 회복 과정을 설명할 때에도 계속 언급했던 내용이죠?

 

MySQL은 innoDB를 사용하는데, MySQL이 설치된 폴더를 찾아보면 로그 파일들을 볼 수 있습니다.

로그 파일을 살펴보면 로그를 이진 file에 기록하여 복제, 복구에 사용되는 binary log,

실제 오류검출에 사용되는 innoDB Log, undo 연산에 사용되는 것은 undo log 등이 있습니다.

 

<틀린 점이 있다면 지적 부탁드립니다. 감사합니다.>

  • 네이버 블러그 공유하기
  • 네이버 밴드에 공유하기
  • 페이스북 공유하기
  • 카카오스토리 공유하기