강의/database

[database] Crash Recovery

하기싫지만어떡해해야지 2025. 6. 7. 19:26

본 게시글은

서울대학교 데이터사이언스대학원 이상원 교수님의

데이터사이언스 응용을 위한 빅데이터 및 지식관리시스템 수업을

학습을 목적으로 재구성하였습니다


이번 학기 데이터베이스 수업의 마지막인

DB crash recovery이다

 

 

DB recovery는 전체 아키텍처에서 저 해당 부분을 담당한다

transaction이 시작되면 각 transaction마다 ID가 부여되는데

모든 작업들은 이 transaction ID를 기준으로 수행된다

recovery도 마찬가지로 transaction ID를 기준으로 수행된다

 

 

우리가 이전 시간에

database의 ACID에 대해서 배웠는데

우리가 오늘 배울 recovery manager은

atomicity와 durability와 관련되어있다

 

 

이전 수업시간부터 계속 봤던 위 예시를 다시 한 번 보자

pi에는 100을 빼고 pj에는 100을 더하고 커밋을 한다

위와 같은 money transfer은 대부분 성공적으로 끝나지만

사용자가 전원을 끈다거나 하는 경우에 에러가 발생할 수도 있다

 

만약 transaction의 결과가 disk까지 내려갔다면

시스템이 전원이 갑자기 꺼지더라도 결과는 그대로 남아있게되지만

DRAM에 있는 것은 전원이 꺼지면 모두 휘발되게된다

 

 

 

위 ppt에는 DBMS가 죽을 수 있는 경우들이 나열되어있다

 

pi를 수정했는데 커밋되지 않은 채로 사용자가 롤백했다면?

이전으로 돌아가야하는 undo 작업이 필요하고

pi와 pj를 수정하고 커밋해서 commit log는 disk에 기록이 됐지만

변경된 데이터 자체는 DRAM에 있을때 crash가 발생했다면?

disk에 있는 log를 바탕으로 다시 transaction을 수행하는

redo 작업을 진행해야한다

또 pi를 수정하고 commit하기 전에 disk에 썼는데 crash가 발생했다면?

그럼 아직 commit이 되지 않았기 때문에 다시 undo를 시켜줘야한다

 

 

만약 dead lock에 걸려있다면 DBMS가

둘 중 한 transaction을 죽여야할 때가 있다

그렇게 된다면 지금까지 자기가 해왔던 것을

undo 해야할 일이 필요하다

이건 그럼 개별 transaction level에서의 recovery이고

전체 시스템 레벨에서의 recovery는 어떻게 될까?

 

 

위 예시를 한 번 살펴보자

T1, T2에서 T5까지 transaction들이 있다

그러고 저 빨간색 부분에서 crash가 발생했다

그럼 T1, T2, T3까지는 모두 다 수행이 되고 커밋도 되었으므로

큰 문제가 발생하지 않는다

이 3개의 transaction에 대해서는 따라서 수행된 결과가

crash를 복구한 이후에도 반영될 수 있는

durability가 보장이 되어야한다

 

그런데 T4, T5 같은 경우는 수행 중간에 crash가 발생한 것이다

따라서 중간에 하다가 멈췄기 때문에

지금까지 수행했던 T4와 T5의 모든 것들은

다시 수행하기 전의 상태로 롤백시켜야한다

이렇게 atomicity를 보장해야한다

 

 

disk write에 대해서 알아보자

위쪽은 buffer pool이고 SQL에서

insert, delete, update를 통해서 수정되는 데이터는

우선 DRAM의 buffer pool에 가장 먼저 반영된다

 

disk는 전원이 꺼져도 휘발되지 않는 영구 저장소로

buffer pool를 거쳐 disk로 내려가게된다

 

DRAM에서 disk로 가는 과정에서는 3가지의 가정이 있다

우선 첫 번째는 데이터의 변경이 일어나는 단위는

Page 단위라는 것.

보통 4KB 혹은 8KB이다

 

두 번째는 update 방법에 대한 것인데

in-place update는 기존의 페이지를 덮어쓰는 방법으로

빠르지만 나중에 복구하기가 어렵다는 단점이 있다

두 번째는 out-of-place 혹은 copy-on-write라고 불리는 방법인데

이는 disk에 overwrite를 하지 않고 새로운데다가 append해서 쓰는 방법을 말한다

그러다가 transaction이 commit이 되면 그때 교체를 하는 방법을 말한다

저장 공간을 많이 필요로 하지만 안전하게 저장할 수 있다

 

그런데 여기서 buffer 자체는 공간이 한정되어있기 때문에

페이지를 교체하는 buffer replacement가 발생하는데

이때는 disk에 내려놓은 다음 교체를 진행해야한다

 

 

buffer에서 disk로 쓰는 방식에 대해서 알아보자

우선 설명은 단일 트랜잭션을 기준으로 설명된다

 

transaction을 커밋할 때 update한 모든 페이지를

다 disk에다 써버리는게 force write at commit이라고 한다

이렇게 하면 transaction이 update한 모든 페이지를

강제로 disk에 내리게 되는데

당연히 매우 단순하고 매우 간단하게 durability가 보장이된다

하지만 너무도 당연하게 이런 식으로 하면 문제가 발생한다는 점인데

disk IO가 많이 발생한다는 것이다

 

 

 

commit 시에 바로 disk에 내리는게 force 정책이었다면

이제 steal 정책에 대해서 알아보자

 

steal은 buffer가 꽉 차서 더이상 새로운 페이지를 저장할 수 없으면

commit되지 않아도 disk로 내리는 방식을 말한다

이렇게 되면 쿼리의 중간과정을 disk에 내릴 수 있어

buffer 공간을 효율적으로 관리할 수 있게 된다

하지만 만약에 수행하다가 commit 이전에 시스템 에러가 나서

롤백을 해야하는 경우에 문제가 발생한다

 

 

 

따라서 steal과 no-force 정책으로 수행하면

훨씬 더 많은 commit을 처리할 수 있어서 퍼포먼스가 좋아진다

하지만 이렇게 되면 recovery를 해야할 일이 많아지고

DBMS가 고려해야할 일이 매우 많아져서

recovery 구현 자체가 복잡해지게 된다

 

 

 

위에서 설명한 steal 정책을 사용하면

rollback시에 다시 데이터를 복원시키기 위해서

undo log를 활용해서 되돌린다

 

따라서 steal을 하면 undo를 관리해야하고

no-force를 하면 disk에 쓰기 전에 crash가 날 경우를 대비해서

redo를 관리해야한다

 

따라서 DBMS는 우리가 모르는 사이에

계속해서 현재 상황을 캡처해서 관리하고있다

 

 

 

그렇다면 No-force 정책을 수행했는데

commit은 되었는데 disk에 내려가기 전에 crash가 발생했다면

이를 어떻게 복구할까?

 

이런 경우에는 log를 확인해서 redo를 수행한다

log에 해당 트랜잭션이 커밋되었다고 되어있는데

디스크에 데이터가 없음을 확인하면

redo log를 따라 해당 트랜잭션을 다시 적용한다

 

 

 

그리고 앞서도 설명했지만

steal일 때 crash가 발생해서

commit 이전인데도 해당 데이터가 디스크에 쓰여졌다면

undo log를 이용해서 다시 이전으로 되돌린다

따라서 이 undo를 위해서 log를 다 캡처해야한다

 

 

 

앞서 간단하게 설명했던 내용이다

 

force로 하면 commit시 바로 disk에 데이터를 쓰기 때문에

매우 간편하고 durability를 간단하게 보장 가능하지만

diskIO가 증가해서 효율성이 떨어진다

 

또 반면에 steal 정책을 사용하면 buffer 효율성은 좋아지지만

atomicity 보장이 복잡해진다

 

따라서 위 2가지 문제를 해결하기 위해서

DBMS는 매 순간 log를 캡쳐해서 사용한다

 

다음 챕터에서는 log에 대해서 자세하게 알아보자

 

 

 

 

이 챕터에서 우리가 알아야할 것은 무엇인가

 

일반적으로 disk가 있고 cache가 있고

별도로 로그를 관리하는 log buffer가 DRAM 이외에 따로 존재한다

모든 Log는 자기의 LSN 주소를 갖고있다

 

위 ppt는 oracle의 구조인데 대부분의 DBMS가 갖고있는 구조기도 하다

보통의 DBMS는 force-log-at-commit을 redo recovery로 사용하고

Write-Ahead Log(WAL)을 undo recovery로 사용한다

 

 

 

DBMS는 모든 transaction에 대해서 어떤 일을 했구나를

redo, undo log로 남긴다

 

앞에서 설명했듯이

update-in-place + steal + no-force 전략을 사용하면

성능면에서는 굉장히 효율적이지만

recovery 구현이 매우 복잡해진다

따라서 recovery를 위해서 undo와 redo log를 모두 남겨야한다

 

그런데 하나의 데이터 페이지에는

여러 개의 transaction들이 섞여있어 recovery가 힘들 수 있다

그래서 pageLSN이란 것을 이용하는데

모든 log에는 LSN(Log Sequence Number)이라는 주소가 존재한다

따라서 해당 log가 적용된 page에는 LSN이 부여되는데

이게 pageLSN이다

 

또한 log도 log buffer라는 DRAM 영역에 우선적으로 저장이되는데

따라서 log를 언제 디스크로 안전하게 보낼 것인지

시점도 매우 중요해진다

 

 

한 페이지 내부에서 데이터가 어떻게 저장되고

데이터가 insert, update, delete가 발생할 때마다

page log가 어떻게 저장되는지를 나타내는 ppt이다

 

데이터들은 각 슬롯마다 저장이 되어있고

해당 page에 log가 적용될때마다

(LSN, Tid, Pi, Offset, Lenght, Before-value, After-value)의 형태로

저장이 된다

 

 

 

DBMS에서 사용하는 로그는

3가지 종류가 있다

 

physical log, logical log, physio-logical log인데

physical log는 변경된 실제 바이트값 자체를 기록하고

logical log는 무엇을 했는지 명령어 수준으로만 기록하고

physio-logical log는 위의 2가지를 동시에 갖고있는 로그이다

 

physical log는 쉽게 설명해서 몇 번째 얼마얼마의 값

이런 구체적인 정보가 적혀있는 것이고

logical log는 어떤 값이었다가 어떤 값으로 이런 명령어를 통해 바꿨다

와 같은 식으로 정보가 적혀있는 것이다

 

위 ppt에서 왼쪽은 logical log가 적혀있는 모습이고

오른쪽은 physio-logical log가 적혀있는 모습이다

 

 

 

지금까지 배웠던 Log에 대한 내용 정리이다

 

database는 disk의 Page와 log로 구성되어있다

DRAM에 있는 page들은 전원이 꺼지면 사라지기때문에 안전하지않다

따라서 Log를 통해서 DBMS의 atomicity와 durability를 보장하도록 하는 것이다

 

혹시나 DBMS에서 system crash가 발생하더라도

redo log를 통해 durability를 보장하고

undo log를 통해 atomicity를 보장한다

 

 

 

위는 DO-UNDO-REDO를 시각적으로 모델링한 것이다

 

어떤 Transaction이 실행되어서

OLD에서 NEW로 DO가 되면

undo log와 redo log가 동시에 기록된다

 

redo log는 OLD에서 NEW를 반영할 떄 사용되고

undo log는 NEW에서 다시 OLD로 rollback할 때 사용된다

 

뭐 이런 로그가 헨젤과 그레텔에서의 과자..?

역할을 한다고 한다 ㅋㅋ

 

 

 

로깅에는 2가지 핵심 프로토콜이 있다

 

첫 번째는 undo log에서의 WAL인데

데이터 페이지를 disk에 쓰기 전에

해당 변경에 대한 undo log가 반드시 먼저 디스크에 기록되어야한다는 것이다

데이터가 디스크에 먼저 쓰였는데 그때 시스템이 죽어서

log가 없다면 다시 복원이 불가능하기 때문이다

 

DBWR(database writer)가 쓰기전에 LGWR(log writer)에

log flush를 먼저 요청한다

이 overhead는 그렇게 크지 않다고 한다

 

두 번째는 redo log에서의 Log-Force-at-Commit이다

트랜잭션이 commit되면 log가 disk에 안전하게

저장이 되어야한다는 것이다

그래야 시스템이 죽어도 안전하게 redo를 수행할 수가 있다

하지만 이는 commit 때마다 disk에 저장하므로

disk IO가 발생해서 병목이 발생할 수가 있다

그래서 이를 예방하기 위해서 group commit이라는 해결책이 있는데

이는 commit이 일정 개수 이상 모이면

한 번에 disk에 저장하는 방법이다

 

 

 

oracle DB의 아키텍처이다

 

 

 

WAL 프로토콜을 시각화한 것이다

 

pj를 disk에 쓰기 전에 log buffer에 있는 log를

먼저 disk로 내리는 것이다

 

 

 

log force at commit이다

transaction이 commit이 되면 log buffer에 있던 log가

disk로 내려가는 것이다

 

 

 

마지막으로 checkpointing이라는 개념을 알아보고

recovery 챕터 및 이번 학기 DBMS 수업을 마무리해보려고한다

 

앞에서 DBMS의 성능을 위해서

no-force 정책을 사용한 다음 system crash가 발생하면

redo log를 이용해서 복구를 했다

 

하지만 만약에 redo할게 너무 많다면 시스템 성능이 떨어질 수가 있다

이런 경우를 예방하기 위해서

일정 시점마다 buffer의 내용을 disk에 저장해두는데

이를 checkpointing이라고 한다

 

 

우리가 LLM 모델 학습시킬 때 

일정 epoch만큼 학습시키면 checkpoint라고 해서

해당 버전을 저장해두곤하는데

그 checkpoint와 유사한 개념이다

 

checkpoint 작업을 할 때는 우선

작업이 언제 시작되었는지를 나타내는

begin_checkpoint를 기록한다

그다음에는 end_checkpoint를 기록하는데

이는 정확한 정보는 아니다

 

왜냐하면 해당 end_checkpoint를 기록하는 시점에도

트랜잭션은 계속해서 진행되고 있고

따라서 해당 end_checkpoint는 begin_checkpoint 시점에서의

snapshot일 뿐 정확한 데이터가 아닐 수 있다

 

또한, 모든 dirty page를 disk에 쓰는 것이 아니라고 한다

원칙상 모든 dirty page를 disk에 쓰면 좋겠지만

실제로는 그렇게 하지 않고

현재까지 진행된 transaction의 일부만 disk에 내린 다음

DBMS가 성능에 맞도록 알아서 나머지들도 disk에 내린다고한다

이걸 그래서 정확하지 않기 때문에

fuzzy checkpoint라고 한다

 

 

 

요약이다

 

지금까지 WAL 프로토콜과 Force-Write Log를 배웠고

checkpointing을 배웠다

 

 

 

ARIES는 현대 DBMS의 표준 recovery 알고리즘이다

뭐 이런 것들이 있는데 크게 설명하지는 않으셨다

시간이 나면 한 번 읽어보라고 하셨다


아무튼 이렇게 recovery까지 마지막으로해서

이번 학기 DBMS 수업이 모두 끝났다

 

마지막으로 갈수록 사실 집중도가 좀 떨어져서..

뭔소린지 잘 몰랐던 부분도 많았지만..

어쨌든 끝-!