15장. 재난 후의 복구 — WAL

만화로 보는 요약 — 먼저 읽어보세요
로그를 먼저 쓴다 → 크래시 후 REDO로 되살린다 → 미완성 작업은 UNDO로 지워 A와 D를 지킨다.
면접 실전 질문: ① WAL이 실제 데이터보다 로그를 먼저 쓰는 이유 ② REDO와 UNDO의 역할 ③ 체크포인트가 복구 시간을 줄이는 방식
배경 — 갚아야 할 빚
여기까지 오면서 저는 빚을 두 번 졌습니다. 5장에서 “되돌리려면 원래 값(undo)을 미리 적어둔다”고 했고, 8장에서 “커밋한 데이터가 안 사라지는 건 로그 덕분(WAL)“이라고 했죠. 그때마다 “자세한 건 15장에서”라고 미뤘어요. 이제 그 빚을 갚을 차례입니다.
지금까지 배운 락·MVCC·낙관적 제어, 그 모든 동시성 장치는 조용한 전제 위에 서 있었어요. “커밋된 데이터는 크래시가 나도 살아남는다.” 그 전제를 실제로 지키는 장치가 바로 이 장의 주인공, WAL과 복구입니다.
스토리 — 먼저 일기부터 쓴다
문제를 다시 봅시다. 데이터는 디스크 여기저기 흩어져 있어서, 커밋마다 그걸 전부 제자리에 쓰면 너무 느립니다(8장). 그렇다고 메모리에만 두면 크래시 때 날아가고요.
해법은 1장 회계사의 지혜였죠. “진짜 장부를 정리하기 전에, 무슨 일이 있었는지 간단한 메모부터 순서대로 빠르게 적는다.” 이 메모가 WAL(Write-Ahead Log), 우리말로 하면 “미리 쓰는 로그”예요. 규칙은 이름 그대로입니다.
실제 데이터의 변경이 디스크에 내려가기 전에, “무엇을 어떻게 바꿀지”를 로그에 먼저 적는다.
로그는 파일 끝에 순차적으로 덧붙이니까 빠릅니다(랜덤 I/O가 아니라 append). 그리고
8장에서 봤듯, 이 로그가 디스크에 안전하게 박히면(fsync) 그때 COMMIT이 성공을
반환해요. 진짜 데이터 페이지는 나중에 천천히 정리됩니다(체크포인트). “먼저 일기,
나중에 정리.”
핵심 — 재부팅, 그리고 되살리기
이제 진짜 질문. 데이터 페이지를 정리하기 전에 전원이 나가면? 재부팅했을 때 DB는 디스크의 로그를 펼쳐 들고, 크래시 직전 상태를 재구성합니다. 이 과정이 복구 (recovery) 이고, 그 정석 알고리즘이 ARIES예요. (엄밀히는 로그를 먼저 훑어 복구 시작점과 미완료 트랜잭션 목록을 파악하는 분석(Analysis) 단계가 앞에 붙지만, 되살리기의 뼈대는 다음 두 걸음입니다.)
- REDO(다시 실행) — “역사를 그대로 반복한다”. 로그에 적힌 변경을 복구 시작점부터 순서대로 재생해, 크래시 직전 상태를 그대로 복원합니다. 심지어 커밋 안 된 트랜잭션의 변경까지 일단 다 재생해요(일단 역사를 똑같이 되돌려 놓는 겁니다).
- UNDO(되돌리기) — “미완성은 지운다”. 그렇게 복원해 놓고 보니, 크래시 순간에 아직
COMMIT하지 못한 트랜잭션이 있죠? 그 미완성 작업들을 undo 로그로 되감아 없앱니다.
결과적으로 재부팅이 끝나면, 커밋된 것은 전부 살아 있고(REDO), 커밋 안 된 것은 흔적도 없는(UNDO) 깨끗한 상태가 됩니다. 지속성(D)과 원자성(A)이 크래시를 건너뛰고 동시에 지켜지는 거예요.
여기서 5장·8장의 두 로그가 드디어 한자리에 모입니다.
| 로그 | 무엇을 적나 | 언제 쓰나 |
|---|---|---|
| undo | 바꾸기 전 옛 값 | 롤백(5장), 복구의 UNDO 단계, MVCC 옛 버전(13장) |
| redo | 바꾼 뒤 새 값 | 지속성(8장), 복구의 REDO 단계 |
그리고 매번 로그를 처음부터 다 재생하면 너무 오래 걸리겠죠. 그래서 8장의 체크포인트가 “여기까지는 이미 디스크에 안전하게 반영됨”이라는 이정표를 찍어둡니다. 복구는 마지막 체크포인트부터만 재생하면 돼요. 로그가 무한정 쌓이지 않게 잘라주는 가위이기도 하고요.
한 문장으로 압축하면 이렇습니다. “먼저 적어두면, 무슨 일이 나도 되돌리거나 되살릴 수 있다.” 트랜잭션의 네 약속 중 A와 D가, 결국 이 두 로그에서 나옵니다.
정리
- WAL(“로그 먼저”) = 실제 데이터 페이지를 고치기 전에 변경을 로그에 먼저 순차
기록. flush되면
COMMIT성공, 진짜 데이터는 나중에(체크포인트). - 크래시 후 복구(ARIES) 두 걸음:
- REDO — 로그를 재생해 크래시 직전 상태 복원(커밋 안 된 것까지 일단 다).
- UNDO — 그중 커밋 못 한 트랜잭션을 undo로 되감아 제거.
- → 커밋된 건 살고(D), 미완성은 사라진다(A).
- undo(옛 값) = 롤백·복구 UNDO·MVCC. redo(새 값) = 지속성·복구 REDO. 체크포인트가 복구 시작점을 당겨 시간을 줄인다.
생각해볼 질문: 여기까지가 제4부, 트랜잭션을 지탱하는 장치들의 내부였습니다. 락·MVCC·낙관적 제어·WAL — 이론과 구현을 다 봤어요. 그런데 이 정교한 장치들이 하나하나 옳게 짜였는데도, 왜 실제 서비스는 멈추고 죽을까요? 문제는 장치의 결함이 아니라 장치들이 서로 부딪치는 방식에 있습니다. 두 트랜잭션이 서로를 붙든 채 영영 멈추거나, 커넥션이 바닥나 서비스가 죽거나. 이제 이론을 내려놓고 현장으로 나갑니다.