트랜잭션을 커밋했다. 그런데 그 직후 서버가 죽었다. 데이터는 살아있는가?
이 질문에 "그렇다"고 답할 수 있으려면 데이터베이스가 내부적으로 어떤 메커니즘을 갖추고 있는지 이해해야 한다. ACID의 D, Durability(지속성)는 그냥 선언이 아니다. Redo Log, Undo Log, Checkpoint라는 구체적인 구현이 그것을 보장한다.
ACID와 복구의 관계
ACID에서 Undo, Redo와 직접 연결된 속성은 세 가지다.
- Atomicity(원자성):
트랜잭션은 전부 반영되거나 전혀 반영되지 않아야 한다. 중간 상태로 남으면 안 된다. 트랜잭션 도중 실패했을 때 지금까지의 변경을 모두 되돌려야 하는데, 이것이 Undo Log의 첫 번째 역할이다. - Isolation(고립성):
트랜잭션이 진행 중인 동안 다른 트랜잭션의 미완료 변경사항이 보여서는 안 된다. InnoDB는 Undo Log에 저장된 이전 버전들을 활용해 각 트랜잭션에게 독립된 스냅샷을 제공한다. 이것이 MVCC다. Undo Log가 없으면 격리 수준 자체를 구현할 수 없다. - Durability(지속성):
커밋된 트랜잭션은 장애가 발생해도 사라지지 않아야 한다. Redo Log가 이를 보장한다.
정리하면 이렇다.
- Undo Log → Atomicity(롤백) + Isolation(MVCC 스냅샷)
- Redo Log → Durability(장애 복구)
Redo와 Undo란 무엇인가
이름 자체가 역할을 설명한다.
Redo: "다시 한다". 커밋된 변경사항이 디스크에 반영되지 못한 채 장애가 났을 때, 해당 변경을 다시 실행해 복구한다. 변경의 이후 상태를 기록한다.
Undo: "되돌린다". 커밋되지 않은 변경사항을 롤백하거나, 다른 트랜잭션에게 변경 이전의 스냅샷을 제공한다. 변경의 이전 상태를 기록한다.

InnoDB의 쓰기 구조
InnoDB는 디스크에 직접 쓰지 않는다. 변경사항은 항상 메모리의 Buffer Pool에 먼저 반영된다. 디스크 I/O는 비싸기 때문에 최대한 모아서 처리한다.
Buffer Pool에서 변경됐지만 아직 디스크에 반영되지 않은 페이지를 Dirty Page라고 한다. "더럽다"는 표현은 메모리와 디스크의 내용이 일치하지 않는 상태를 의미한다. Dirty Page는 언젠가 디스크에 내려써야 하는데, 이 시점이 바로 뒤에서 설명할 Checkpoint다.

문제는 여기서 생긴다. 커밋은 됐는데 Dirty Page가 아직 디스크에 안 내려간 상태에서 서버가 죽으면? 데이터가 사라진다.
Redo Log가 이를 막는다.
Redo Log — "커밋한 것은 반드시 살아남는다"
Redo Log는 변경사항의 이후 상태를 기록한다. "이 페이지의 이 오프셋을 이 값으로 바꿔라"는 물리적 변경 내역이다.
커밋 시점에 InnoDB는 Dirty Page를 디스크에 쓰지 않더라도, Redo Log는 반드시 디스크에 먼저 기록한다.
이것을 WAL(Write-Ahead Logging) 이라고 한다. 로그를 데이터보다 먼저 쓴다는 원칙이다.
WAL을 사용하는 이유는 복구만이 아니다. I/O 성능 때문이기도 하다.
변경사항을 데이터 파일에 바로 쓰려면 해당 페이지가 디스크의 어느 위치에 있는지 찾아야 한다. 테이블의 여러 페이지가 디스크 곳곳에 흩어져 있으므로 이것은 Random I/O다.
Random I/O는 디스크 헤드가 이리저리 이동해야 하므로 느리다. 반면 Redo Log는 파일 끝에 순서대로 append한다. 이것은 Sequential I/O다. 디스크 헤드 이동 없이 순서대로 쓰기만 하면 되므로 Random I/O보다 압도적으로 빠르다.

즉 WAL은 "복구를 위해 어쩔 수 없이 감수하는 비용"이 아니라, 성능과 복구 안정성을 동시에 얻는 설계다.

장애 이후 재시작하면 InnoDB는 Redo Log를 읽어 커밋된 변경사항을 Data File에 재적용한다. 이것이 Redo(재실행) 다.
Undo Log — "커밋 안 한 것은 반드시 되돌린다"
Undo Log는 변경사항의 이전 상태를 기록한다. UPDATE를 하기 전 원래 값, DELETE 전 원래 row가 여기 저장된다.
Undo Log는 두 가지 목적으로 쓰인다.
- 첫 번째: 롤백
트랜잭션이 실패하거나 ROLLBACK이 호출되면, Undo Log를 역순으로 적용해 변경 전 상태로 되돌린다. - 두 번째: MVCC
다른 트랜잭션이 변경 이전의 데이터를 읽어야 할 때, Undo Log의 버전 체인을 탐색해 스냅샷을 제공한다. Read View가 가리키는 버전을 Undo Log에서 찾아 반환하는 것이다.

버전 체인이 길어질수록 SELECT가 느려진다. 오래된 트랜잭션이 Read View를 붙잡고 있으면 Undo Log를 정리할 수 없기 때문이다.
Checkpoint — "어디서부터 복구하면 되는가"
Redo Log는 무한히 쌓일 수 없다. InnoDB는 주기적으로 Buffer Pool의 Dirty Page를 디스크에 내려쓰는데, 이 시점을 Checkpoint라고 한다.
Checkpoint의 핵심은 "이 시점 이전의 변경사항은 이미 디스크에 있다"는 보장이다. 따라서 Checkpoint 이전의 Redo Log는 복구에 필요 없어지고, 해당 공간을 재사용할 수 있다.

- Checkpoint가 자주 발생하면:
복구 시 재적용할 Redo Log 범위가 줄어 복구가 빠르다. 하지만 Dirty Page를 자주 디스크에 쓰므로 I/O 부하가 높아진다.
- Checkpoint가 드물게 발생하면:
I/O 부하는 낮지만, 장애 시 재적용해야 할 Redo Log 범위가 넓어져 복구 시간이 길어진다.
장애 복구 시나리오 (Recovery Flow)
다음과 같은 상황을 가정한다. T1은 체크포인트 이전에 시작해 커밋까지 완료됐고, T2는 체크포인트 이후 시작했지만 커밋 전에 CRASH가 발생했다.

※ 체크포인트: Buffer Pool의 Dirty Page를 디스크에 완전히 반영한 '최후의 안전 지점'. 이 시점 이전의 변경사항은 디스크에 보장된다.
1단계 — REDO: 체크포인트 이후의 모든 Redo Log를 읽어 재실행한다. 커밋이 완료된 T1과 진행 중이던 T2 모두 로그를 따라 재실행해 CRASH 직전 상태를 디스크에 재현한다.
2단계 — UNDO: Redo가 끝난 뒤 커밋 마크가 없는 미완료 트랜잭션을 식별한다. T2의 Undo Log를 역순으로 적용해 T2의 모든 변경을 취소하고 데이터를 이전 상태로 되돌린다.
"T1은 성공 기록을 살려내고, 미완료인 T2는 흔적까지 지운다."

두 단계가 반드시 이 순서여야 하는 이유가 있다. Undo를 먼저 하면 T2의 변경사항을 지웠는데, 그 이후 Redo가 T2를 다시 재실행해버리는 모순이 생긴다. Redo로 CRASH 직전 상태를 완전히 복원한 뒤, 그 위에서 Undo로 미완료 트랜잭션만 걷어내는 것이 올바른 순서다.
네 가지 케이스로 이해하는 복구
장애 발생 시점에 따라 복구 결과가 달라진다.
| 상황 | Redo | Undo | 결과 |
| 커밋 O, 디스크 반영 O | 불필요 | 불필요 | 정상 |
| 커밋 O, 디스크 미반영 | 재적용 | 불필요 | 복구됨 |
| 커밋 X, 디스크 미반영 | 불필요 | 불필요 | 자연 소멸 |
| 커밋 X, 디스크 반영 O | 재적용 후 | 롤백 | 롤백됨 |
세 번째 케이스(커밋 X, 디스크 미반영)는 그냥 사라지면 되므로 아무것도 안 해도 된다. 가장 까다로운 케이스는 네 번째다.
Redo로 먼저 디스크를 최신 상태로 만든 뒤, 미완료 트랜잭션임을 확인하고 Undo로 롤백한다.
주요 DBMS별 구현 방식
Redo/Undo의 개념은 동일하지만 DBMS마다 구현 방식이 다르다.
| DBMS | Redo 구현 | Undo 구현 |
| MySQL (InnoDB) | Redo Log File (ib_logfile) | Undo Segment (언두 영역) |
| Oracle | Redo Log Files (Online Redo) | Undo Tablespace (전용 공간) |
| PostgreSQL | WAL (XLOG, 지속성 전용) | 물리적 로그 없음 (MVCC로 해결) |
PostgreSQL은 InnoDB와 MVCC 구현 방식이 근본적으로 다르다.
InnoDB는 변경 시 원본 데이터를 덮어쓰고 이전 버전을 Undo Log에 보관하지만, PostgreSQL은 원본을 덮어쓰지 않고 새 버전을 별도 공간에 추가한다(Heap 방식).
이전 버전이 데이터 파일 안에 그대로 남아있으므로 별도의 Undo 파일이 필요 없다. 대신 오래된 버전을 주기적으로 정리하는 VACUUM 작업이 필요하다.
정리
Redo는 성공한 트랜잭션의 지속성을 위해 '변경 후의 값'을 기록하며, 장애 후 데이터를 재현하는 데 사용된다.
Undo는 원자성과 고립성을 위해 '변경 전의 값'을 기록하며, 롤백 시 데이터를 되돌리거나 MVCC에서 과거 버전을 보여주는 데 사용된다.
복구 시에는 WAL 메커니즘에 따라 Redo를 수행해 사고 직전 상태를 만들고, 커밋되지 않은 트랜잭션을 Undo로 제거하여 최종 무결성을 확보한다.
참고 영상