크래시 직후 데이터 파일은 어중간한 상태다. 커밋됐는데 아직 안 내려간 변경도, 커밋도 안 됐는데 미리 내려간 변경도 섞여 있다. 복구는 이를 두 방향으로 바로잡는다.
두 방향
redo (롤포워드)
- 커밋은 됐지만 데이터 파일에 아직 반영 안 된 변경
- 로그를 보고 다시 적용한다
- 지속성(Durability) 보장. 커밋된 건 살린다
undo (롤백)
- 커밋 안 된 채 데이터 파일에 미리 반영된 변경
- 로그를 보고 되돌린다
- 원자성(Atomicity) 보장. 미완성은 없던 일로
왜 '미리 내려간' 변경이 있나
버퍼가 가득 차면 DB는 커밋 여부와 무관하게 더티 페이지를 디스크로 내보낸다(steal 정책). 그래야 메모리를 재활용할 수 있기 때문이다. 그 결과 미완성 변경이 디스크에 남을 수 있고, 그래서 undo가 필요하다.
ARIES — 표준 알고리즘
- ① 분석(Analysis) — 마지막 체크포인트부터 로그를 읽어
- 크래시 시점에 진행 중이던 트랜잭션과 더티 페이지를 파악
- ② redo — 로그의 모든 변경을 순서대로 다시 적용한다
- (커밋 여부를 가리지 않고 전부 — 상태를 크래시 직전으로 되돌린다)
- ③ undo — 그중 커밋되지 않았던 트랜잭션만 골라 역순으로 되돌린다
②가 직관에 반한다 — 커밋 안 된 것까지 일단 다 적용한다. 그래야 로그와 데이터의 상태가 일치해 ③을 정확히 수행할 수 있기 때문이다(repeating history).
이체 예시로 보면
A 에서 빼고 B 에 더하는 중 전원이 나갔다
| 커밋 전이었다 | → undo 로 둘 다 원복 | 돈이 증발하지 않는다 |
|---|---|---|
| 커밋 후였다 | → redo 로 둘 다 확정 | 돈이 사라지지 않는다 |
어느 쪽이든 '한쪽만 반영된 상태' 로는 끝나지 않는다
복구가 도는 걸 확인하기
-- PostgreSQL 로그
LOG: database system was not properly shut down; automatic recovery in progress
LOG: redo starts at 0/1A2B3C4
LOG: redo done at 0/1F8E220
LOG: database system is ready to accept connections
-- MySQL 로그
[Note] InnoDB: Starting crash recovery.
[Note] InnoDB: Apply batch completed
redo starts at 부터 redo done at 까지의 거리가 마지막 체크포인트 이후 쌓인 로그의 양이다. 이 구간이 길면 복구가 오래 걸린다.
면접 함정
- ❌ "undo는 롤백에만 쓰인다" → InnoDB에서는 MVCC 읽기에도 함께 쓰인다.
- ❌ "redo는 커밋된 것만 다시 적용한다" → ARIES는 일단 전부 적용한 뒤 미커밋분을 undo한다.
복구 시간을 좌우하는 것
-- MySQL: 마지막 체크포인트 이후 쌓인 양(checkpoint age)이 복구 시간을 정한다
SHOW ENGINE INNODB STATUS\G
-- LOG 섹션
-- Log sequence number 현재 위치
-- Last checkpoint at 마지막 체크포인트
-- → 둘의 차이가 크면 크래시 복구가 오래 걸린다
SHOW VARIABLES LIKE 'innodb_redo_log_capacity'; -- 크면 처리량↑ 복구시간↑
redo 로그 용량은 처리량과 복구 시간의 트레이드오프다. 크게 잡으면 체크포인트가 뜸해져 평상시가 빠르지만, 크래시 시 되짚을 로그가 많아진다.
롤백도 공짜가 아니다
100만 행을 UPDATE 하다가 취소하면?
- undo 를 100만 번 역순으로 적용해야 한다
- 롤백이 원래 작업보다 오래 걸리는 일이 흔하다
그래서 대량 작업은 나눠서 커밋한다 (배치 1만 건씩)