WAL은 데이터 페이지보다 로그를 왜 먼저 쓸까? 변경 기록과 체크포인트

반응형

데이터베이스가 주문 상태 한 줄을 바꿀 때 디스크의 데이터 페이지를 곧바로 제자리에 덮어쓰면 단순해 보인다. 하지만 여러 페이지를 바꾸는 중 전원이 끊기면 어느 페이지까지 반영됐는지 알기 어렵다. 작은 위치를 자주 덮어쓰는 작업도 저장장치에 불리할 수 있다.

WAL(Write-Ahead Log: 데이터 페이지를 바꾸기 전에 변경 내용을 순서대로 먼저 남기는 로그)은 커밋의 내구성과 재시작 복구의 기준을 만든다. 이름의 Ahead는 변경 기록이 실제 데이터 페이지 반영보다 앞서야 한다는 순서 규칙을 뜻한다.

데이터 페이지는 메모리에서 먼저 바뀔 수 있다

데이터베이스는 자주 읽는 디스크 페이지를 버퍼 풀(buffer pool: 데이터와 인덱스 페이지를 메모리에 보관하고 재사용하는 데이터베이스 캐시)에 올린다. UPDATE가 실행되면 메모리의 페이지가 먼저 바뀌고 더티 페이지가 된다.

디스크 데이터 페이지: status = pending
           ↓ 읽기
버퍼 풀 페이지: status = pending
           ↓ UPDATE
버퍼 풀 더티 페이지: status = paid

더티 페이지를 매 트랜잭션마다 즉시 원래 디스크 위치에 쓰면 무작위 쓰기가 많아지고 커밋이 느려진다. 반대로 너무 늦게 쓰면 장애 때 메모리 변경을 잃는다. WAL은 데이터 페이지와 커밋의 시점을 분리한다.

로그를 먼저 안정적으로 남기면 데이터 페이지는 나중에 써도 된다

WAL 레코드(log record: 어떤 트랜잭션이 어느 데이터에 어떤 변경을 했는지 복구에 필요한 정보를 담은 기록)는 로그 끝에 순서대로 추가된다. 순차 쓰기는 흩어진 데이터 페이지를 각각 갱신하는 것보다 저장장치에 효율적인 경우가 많다.

1. 버퍼 풀의 데이터 페이지 변경
2. 해당 변경의 WAL 레코드 생성
3. 커밋에 필요한 WAL을 안정적인 저장장치에 기록
4. 커밋 성공 응답
5. 더티 데이터 페이지는 나중에 기록 가능

핵심 규칙은 데이터 페이지가 디스크에 기록되기 전에 그 변경을 설명하는 WAL이 먼저 안정적으로 남아야 한다는 것이다. 그래야 데이터 페이지가 일부만 반영된 채 재시작해도 로그를 기준으로 상태를 복원할 수 있다.

LSN은 로그와 페이지의 진행 위치를 연결한다

LSN(Log Sequence Number: WAL 레코드가 로그 안에서 놓인 순서를 나타내는 증가하는 위치 번호)은 복구가 어디까지 진행됐는지 판단하는 기준이 된다. 데이터 페이지에도 마지막으로 반영된 LSN을 기록할 수 있다.

WAL
LSN 100: 주문 7 상태 pending → paid
LSN 101: 결제 기록 추가

데이터 페이지의 page LSN = 100

재시작할 때 페이지가 LSN 100까지 반영된 상태라면 같은 변경을 어떻게 다룰지 판단할 수 있다. 실제 데이터베이스의 WAL 형식과 복구 알고리즘은 다르지만 단순한 시간보다 단조롭게 증가하는 로그 위치를 기준으로 삼는 생각은 공통적이다.

커밋은 모든 데이터 페이지가 디스크에 있다는 뜻이 아니다

커밋 시점에 필요한 WAL이 안정적으로 기록되면 트랜잭션 성공을 응답할 수 있다. 데이터 페이지는 이후 백그라운드에서 디스크에 내려갈 수 있다.

커밋 직후
WAL 디스크: 변경과 커밋 기록 있음
데이터 디스크: 일부는 이전 값일 수 있음
메모리: 최신 더티 페이지 있음

이 상태에서 장애가 나면 재시작 과정이 WAL을 다시 적용해 최신 커밋 상태를 만든다. 이를 redo(redo: 커밋됐지만 데이터 페이지에 반영되지 않은 변경을 로그에서 다시 적용하는 복구 작업)라고 한다.

커밋되지 않은 변경이 데이터 페이지에 먼저 내려갔다면 되돌릴 정보가 필요할 수 있다. undo(undo: 완료되지 않은 트랜잭션의 영향을 이전 상태로 되돌리는 복구 작업)를 WAL이나 별도 버전 정보로 수행한다. 데이터베이스마다 redo와 undo의 세부 구조는 다르다.

체크포인트는 복구가 시작할 범위를 줄인다

WAL이 계속 쌓이면 재시작 때 처음부터 모든 기록을 읽을 수는 없다. 체크포인트(checkpoint: 복구에 필요한 기준 위치와 데이터 페이지 진행 상태를 기록해 재생 범위를 줄이는 작업)는 “이 지점 이전 변경은 어느 정도 데이터 페이지에 반영됐다”는 기준을 만든다.

오래된 WAL ───── 체크포인트 ───── 최신 WAL
                      ↑
              복구가 참고할 시작 기준

체크포인트가 모든 더티 페이지를 한꺼번에 쓰면 I/O가 몰릴 수 있다. 실제 시스템은 페이지 기록을 나누거나 체크포인트 주기를 조정한다. 체크포인트를 자주 하면 평소 쓰기 부담이 늘고 드물게 하면 장애 뒤 복구 시간이 길어진다.

그룹 커밋은 여러 트랜잭션의 로그 동기화를 묶는다

저장장치에 로그를 동기화하는 작업은 비싸다. 그룹 커밋(group commit: 가까운 시간에 커밋하는 여러 트랜잭션의 WAL 동기화를 한 번의 저장장치 작업으로 묶는 방식)은 내구성 경계를 유지하면서 처리량을 높인다.

트랜잭션 A 커밋 요청 ┐
트랜잭션 B 커밋 요청 ├→ WAL 한 번 동기화 → A, B, C 응답
트랜잭션 C 커밋 요청 ┘

대신 첫 요청이 아주 짧게 기다릴 수 있다. 개별 지연과 전체 처리량 사이의 선택이다. 커밋 지연을 볼 때 쿼리 실행 시간뿐 아니라 WAL 동기화 대기와 저장장치 지연을 따로 확인해야 한다.

WAL도 무한히 보관할 수는 없다

체크포인트와 백업, 복제본이 필요로 하는 로그 위치보다 오래된 WAL은 제거하거나 보관 정책에 따라 아카이브할 수 있다. 복제본이 너무 늦거나 백업 복구가 특정 WAL 구간을 필요로 하면 바로 지우지 못한다.

WAL 보관량 증가는 단순한 로그 청소 문제가 아닐 수 있다. 복제 지연, 장시간 트랜잭션, 아카이브 실패처럼 누군가 오래된 로그 위치를 계속 필요로 하는지 먼저 찾아야 한다.

이해 확인 질문과 답변

WAL을 쓴다면 데이터 페이지는 디스크에 쓰지 않아도 될까?

아니다. WAL은 데이터 페이지를 영원히 대신하지 않는다. 더티 페이지를 나중에 기록할 수 있게 하고 장애 뒤 빈 부분을 복구하는 기준을 제공한다. 결국 데이터 페이지도 디스크에 반영하고 오래된 WAL을 정리해야 한다.

커밋 성공인데 디스크 데이터 페이지가 이전 값일 수 있는 이유는 무엇일까?

커밋의 내구성 기준을 데이터 페이지 전체가 아니라 필요한 WAL의 안정적 기록으로 잡았기 때문이다. 장애가 나면 WAL을 재생해 커밋된 최신 상태를 복원한다.

체크포인트를 자주 만들수록 항상 좋을까?

복구 범위는 짧아지지만 평소 더티 페이지 쓰기와 메타데이터 작업이 늘 수 있다. 목표 복구 시간과 정상 부하의 I/O를 함께 보고 주기를 정한다.

WAL 파일이 계속 커지면 바로 삭제해도 될까?

안 된다. 체크포인트, 복제본, 백업이 어느 로그 위치까지 필요로 하는지 확인해야 한다. 필요한 WAL을 지우면 복제나 시점 복구가 불가능해질 수 있다.

AI에게 이렇게 요청할 수 있다

주문 상태 UPDATE가 버퍼 풀의 더티 페이지와 WAL에 기록되는 순서를 보여 줘. 커밋 직후 데이터 페이지는 이전 값이지만 WAL은 저장된 상태에서 장애가 났을 때 redo와 체크포인트가 어떻게 복구 범위를 정하는지 설명해 줘.

WAL의 핵심은 로그 파일이 있다는 사실이 아니다. 데이터 페이지보다 먼저 복구 가능한 기록을 남긴다는 순서 규칙으로 커밋 응답과 실제 페이지 기록 시점을 분리하는 데 있다.

반응형
KEEP READING
카테고리 전체 보기 →

댓글