8장. 한번 약속하면 영원히 — 지속성

만화로 보는 요약 — 먼저 읽어보세요
COMMIT 직후 서버가 죽어도 결과는 남아야 한다 → WAL은 변경을 로그에 먼저 안전하게 적는다 → 재부팅 후 redo로 커밋된 변경을 되살린다.
면접 실전 질문: ① 지속성이 보장하는 시점 ② WAL과 fsync의 역할 ③ undo와 redo의 차이
배경 — “완료” 다음 순간, 전원이 나갔다
7장 끝에서 물었죠. COMMIT으로 무사히 끝냈는데, 바로 다음 순간 서버 전원이 나가면?
결제 화면에 “완료”가 뜬 걸 고객은 분명히 봤습니다. 그런데 그 데이터가 아직 메모리에만
있고 디스크에 안 쓰였다면 — 재부팅 후 주문이 사라져 있을 수 있어요.
이건 절대 일어나면 안 되는 일입니다. “완료”라고 말한 이상, 세상이 어떻게 되든 그
약속은 지켜져야 해요. 이게 ACID의 마지막 글자, D, 지속성(Durability) 입니다.
한번 COMMIT된 데이터는 정전·크래시가 와도 살아남는다.
스토리 — 커밋된 것은 죽지 않는다
지속성의 정의는 한 문장이에요. COMMIT이 성공을 반환한 그 순간부터, 그 변경은
영구적이다. 서버가 죽어도, 전기가 나가도, 재부팅하면 그 데이터는 그대로 있어야
합니다.
방법은 뻔해 보입니다. “커밋할 때 디스크에 확실히 써버리면 되잖아?” 맞아요. 문제는 속도입니다. 데이터는 디스크 여기저기 흩어져 있어서, 매 커밋마다 그 흩어진 곳을 전부 찾아가 쓰면(랜덤 I/O) 끔찍하게 느립니다. 그렇다고 안 쓰면 크래시 때 날아가고요. 안전과 속도가 또 부딪칩니다.
여기서 1장 회계사의 지혜가 다시 등장합니다. 회계사는 큰 거래를 장부에 곱게 옮기기 전에, 먼저 간단한 메모(전표) 부터 빠르게 휘갈겼어요. 무슨 일이 있었는지 순서대로. DB도 똑같이 합니다. “진짜 데이터를 정리하기 전에, 무슨 변경을 할지 로그부터 먼저 적는다.” 이게 그 유명한 WAL(Write-Ahead Logging), “로그 먼저” 원칙이에요.
핵심 — 먼저 적어두면, 잃지 않는다
COMMIT 순간 실제로 벌어지는 일의 큰 그림은 이렇습니다.
1. 변경 내용을 WAL 로그에 append (디스크에 순차 기록 — 빠름)
2. 그 로그를 디스크에 확실히 flush (fsync) ← 여기까지 되면 COMMIT 성공 반환
3. 진짜 데이터 페이지는 나중에 천천히 갱신 (체크포인트 — 로그 내용을 실제 데이터에 반영해두는 지점)핵심은 2번입니다. 로그가 디스크에 안전하게 박히는 순간, COMMIT은 성공을
반환합니다. 흩어진 진짜 데이터를 다 정리할 때까지 기다리지 않아요. 로그는 순차
기록이라 빠르니까, 안전과 속도를 동시에 잡는 거죠.
그럼 3번을 하기 전에 전원이 나가면요? 걱정 없습니다. 재부팅하면 DB가 로그를 다시 읽어(redo) 못다 한 정리를 마저 해줍니다. “무슨 변경을 할지” 순서대로 다 적혀 있으니, 그대로 재생하면 크래시 직전 상태가 복원돼요. 이렇게 로그로 되살리는 과정이 복구 (recovery) 이고, 이 이야기의 진짜 깊은 부분은 15장에서 파헤칩니다.
5장에서 롤백을 위해 “원래 값(undo)“을 적어둔다고 했죠? 지속성을 위해선 “새 값(redo)“을 적어둡니다. undo는 되돌리려고, redo는 되살리려고. 이 두 로그가 트랜잭션의 안전을 떠받치는 두 기둥이에요. (사실 크래시 복구도 둘을 함께 씁니다 — redo로 되살린 뒤, 커밋 못 하고 죽은 트랜잭션은 undo로 되돌리죠. 이 정교한 춤은 15장에서.)
한 가지 덧붙이면, 지속성도 다이얼이 있습니다. 일부 DB는 “로그 flush를 매 커밋마다 말고 1초에 한 번만” 같은 옵션을 줘요.
-- 지금 내 MySQL의 지속성 다이얼 확인
SHOW VARIABLES LIKE 'innodb_flush_log_at_trx_commit'; -- 1 = 매 커밋마다 fsync (완전 지속성)
-- 속도를 위해 다이얼을 살짝 푼다 (크래시 시 최근 커밋 몇 개를 잃을 수 있음)
SET GLOBAL innodb_flush_log_at_trx_commit = 2;PostgreSQL이라면 synchronous_commit이 같은 역할이에요. 조금 더 빠른 대신, 크래시
때 최대 1초 안팎의 최근 커밋을 잃을 위험을 지는 거죠(대신 DB가 깨지진 않습니다 —
최근 몇 개가 없어질 뿐). 격리성처럼, 지속성도 안전과 속도 사이의 선택입니다.
정리
- 지속성(Durability) =
COMMIT이 성공을 반환한 데이터는 정전·크래시에도 살아남는다. - 매 커밋마다 흩어진 데이터를 다 쓰는 건 느리다 → WAL(“로그 먼저”): 변경을 순차 로그에 먼저 적고 flush되면 커밋 성공, 진짜 데이터는 나중에 정리(체크포인트).
- 크래시 후 재부팅 시 로그를 redo로 재생해 복구한다(→ 15장에서 深掘).
- undo(되돌리기, 5장) + redo(되살리기, 8장) = 트랜잭션 안전의 두 기둥.
- 지속성에도 안전↔속도 다이얼이 있다(
flush주기 옵션).
생각해볼 질문: 이걸로 ACID 네 글자를 다 봤습니다. A(전부 아니면 전무), C(규칙은 안 깨진다), I(혼자인 척), D(영원히). 그런데 우리는 가장 어렵다던 I(격리성) 를 “큰 그림”만 보고 지나쳤어요. 격리를 느슨하게 풀면 튀어나온다던 그 괴현상들은 정확히 어떻게 생겼을까요? 이제 제3부, 동시성의 전쟁터로 들어갑니다.