WAL(Write-Ahead Logging) 알아보기 2편 - Redo Log와 WAL

머랭·2026년 1월 19일
post-thumbnail

0. 서론

안녕하세요 머랭입니다.
지난 포스팅에선, InnoDB가 버퍼를 관리하기 위해 STEAL & No-FORCE 정책을 사용한다는 것을 알아보았습니다.

이번 포스팅에서는 No-FORCE 정책을 유지하면서, 동시에 Durability를 보장하기 위한 Redo Log와 WAL(Write-Ahead Logging)에 대해 설명드리겠습니다.


1. Redo Log

InnoDB는 랜덤 I/O를 최소화하기 위해, 트랜잭션이 커밋 이후에도 더티 페이지를 즉시 디스크에 반영하지 않고 버퍼에 보관하는 No-FORCE 정책을 사용합니다.
그러나, No-FORCE 정책은 Durability를 보장할 수 없다는 단점이 존재합니다.

InnoDB는 No-FORCE 정책을 사용하면서도 Durability를 보장하기 위해 Redo Log를 사용합니다.
Redo Log는 페이지 내의 데이터 변경 기록을 기록하고, 추후 다시 수행될 수 있도록 하기 위해 존재하는 로그 파일입니다.
장애가 발생하더라도, 재부팅 후 Redo Log를 읽어 변경사항을 다시 수행하면 커밋된 트랜잭션에 대한 데이터는 완벽히 복구됩니다.


2. 구조

Redo Log는 Redo Log Block으로 이루어지며, Redo Log Block은 Redo Log Record들로 이루어집니다.
Redo Log Record의 구조는 다음과 같습니다.

  • Type: 변경 작업의 성격(Insert, Update, Delete 등)을 나타내는 타입 코드입니다.
  • Space ID: 데이터가 변경된 테이블스페이스의 ID입니다.
  • Page Number: 변경이 발생한 페이지의 번호입니다.
  • Offset: 페이지 내에서 실제 데이터가 수정된 시작 위치입니다.
  • Data: 변경된 실제 데이터 내용입니다.

3. WAL(Write-Ahead Logging)과 로그 버퍼

WAL(Write-Ahead Logging)은 데이터를 변경하기 전, 변경 로그를 먼저 기록한다는 원칙입니다.
트랜잭션이 커밋되기 전에 Redo Log를 먼저 기록함으로써 Durability를 보장할 수 있습니다.
장애가 발생하더라도, 재부팅 후 Redo Log를 읽어 변경사항을 다시 수행하면 커밋된 트랜잭션에 대한 데이터를 복구할 수 있습니다.

WAL의 핵심은, “트랜잭션 커밋 전 Redo Log를 저장해야 한다”는 것입니다.
트랜잭션이 커밋되기 전, Redo Log를 어떤 수준까지 저장할 것인지 이해하기 위해선 로그 버퍼에 대해 알아야 합니다.

이후 포스팅에서 다룰 예정이지만, 사실 Redo Log Record는 트랜잭션 커밋 시점이 아닌 MTR(Mini-Transaction)이라는 최소 단위 트랜잭션 커밋 시마다 생성됩니다.

만약, 매 트랜잭션이 끝날 때마다 모든 Redo Log Recoord들이 디스크(혹은 OS 페이지 캐시)저장되어야 한다면 계속해서 랜덤 I/O가 발생하기 때문에 매우 비효율적일 것입니다.


이 문제를 해결하기 위해 도입된 것이 로그 버퍼입니다.
로그 버퍼는 트랜잭션 진행 중 발생하는 Redo Log Record들을 디스크에 쓰기 전 메모리에 임시로 저장하는 버퍼입니다.
트랜잭션 과정에서 발생한 Redo Log Record들을 모아 블록 단위로 묶어 I/O함으로써, 디스크 I/O 횟수를 줄일 수 있습니다.

그러나, 로그 버퍼는 메모리 기반 버퍼이기 때문에 버퍼와 디스크 사이의 데이터 불일치가 발생하는 시점이 존재합니다.
InnoDB는 데이터 불일치가 발생하는 기간을 조절할 수 있는 innodb_flush_log_at_trx_commit 속성을 제공합니다.
innodb_flush_log_at_trx_commit 속성은 트랜잭션이 성공하기 위해서 로그 버퍼의 블록을 어느 단계까지 작성해야 하는지 정하는 속성입니다.

커밋 시유실 범위 (장애 시)성능 오버헤드
1 (기본)write() + fsync() 수행없음높음 (디스크 I/O 대기)
2write()만 수행최대 1초중간 (I/O 대기 없음)
0아무 일도 하지 않음최대 1초낮음 (System Call 없음)

1: 모든 블록이 디스크까지 작성되어야 트랜잭션이 성공할 수 있습니다.

  • 데이터 유실은 없으나, 디스크 쓰기 속도가 전체 트랜잭션의 병목 지점이 될 수 있습니다.
  • 먼저 완료된 트랜잭션으로 인해 디스크 쓰기 작업이 진행중일 때 다른 트랜잭션이 커밋된다면, 로그 버퍼에 Redo Log Record를 쌓아두었다가 디스크 쓰기 작업이 끝나면 곧바로 디스크에 작성합니다.

2: 모든 블록이 OS의 페이지 캐시에 작성되어야 트랜잭션이 성공할 수 있습니다.

  • 실제 디스크 기록은 OS가 수행하므로 빠르지만, 서버 장애 발생 시 OS 캐시는 유실됩니다.
  • 백그라운드 스레드가 1초 주기로 fsync()를 호출합니다.

0: 모든 블록이 로그 버퍼에 작성되어야 트랜잭션이 성공할 수 있습니다.

  • 백그라운드 스레드가 1초 주기로 로그 버퍼의 모든 블록을 디스크에 작성합니다.
  • DB 엔진이나 OS 중 하나만 비정상 종료되어도 마지막 1초간의 커밋은 무효화됩니다.

4. 로그 그룹

InnoDB는 Redo Log의 순차 I/O를 위해 여러 개의 Redo Log 파일을 로그 그룹으로 묶어서 관리합니다.

로그 그룹은 하나 이상의 물리적인 Redo Log 파일들을 논리적인 바이트 스트림으로 관리할 수 있게 도와주는 컨테이너입니다.
로그 그룹은 Redo Log 파일들을 원형 큐 형태로 관리합니다. N번 파일의 마지막 바이트 다음 바이트는 N+1번 파일의 첫 번째 바이트로 이어지도록 구성합니다.

바이트를 스트림 형태로 다루는 것은 ‘논리적 추상화’ 입니다.
논리적으로 파일 간 데이터 흐름이 이어질 수 있어도, 파일 단편화로 인해 순차 I/O가 발생하지 않을 수 있습니다.
InnoDB는 데이터베이스 초기화 시점에 전체 Redo Log 파일의 크기를 미리 할당받습니다.
이 과정에서, 운영체제는 최대한 연속된 물리 공간을 할당하게 되므로 파편화를 최대한 방지하고, 디스크 헤더 이동 시간을 최소화합니다.

파일이 가득 차면 다음 파일로 넘어가고, 마지막 파일까지 가득 차면 다시 첫 번째 파일의 처음으로 돌아와서 데이터를 덮어씁니다.

무조건 덮어쓰는 것은 아닙니다. 덮어쓰려는 Redo Log가 Inactive 상태여야 합니다.
Active: 반영되지 않은 Redo Log Record가 존재해 추후 복구에 해당 Redo Log 파일이 필요할 수 있는 상태.
Inactive: 모든 Redo Log Record가 반영되어 더 이상 사용되지 않는 상태.

덮어써야 하는 Redo Log가 Active 상태라면, InnoDB는 새로운 트랜잭션을 잠시 멈추고(Blocking), Redo Log 내 Redo Log Record와 연결된 더티 페이지를 디스크에 쓰기 시작합니다.

Redo Log의 크기와 갯수 옵션을 조절하여 Blocking 문제를 최대한 방지할 수 있습니다.
참고: https://dev.mysql.com/doc/refman/8.4/en/innodb-parameters.html#sysvar_innodb_log_file_size


5. LSN(Log Sequence Number)

Redo Log Record는 더티 페이지가 발생할 때마다 생성됩니다.
이로 인해 장애 복구 시간이 크게 늘어나고, 무한한 Redo Log 저장 공간을 요구하게 됩니다.

이를 해결하기 위해 LSN(Log Sequence Number) 이 도입되었습니다.
LSN은 데이터베이스 생성 시점부터 현재까지 발생한 Redo Log의 총 누적 바이트 합계를 나타내는 값으로, Redo Log Record와 페이지에 부여됩니다.

LSN은 Global LSN을 통해 생성되며, 처음엔 Redo Log Record에 부여됩니다. 이후, Redo Log Record가 수정하는 페이지 헤더에 똑같은 LSN을 기록합니다.
이 시점부터 해당 페이지는 더티 페이지가 됩니다.


LSN이 사용되는 이유는 두 가지입니다.

1. Redo Log Record의 LSN과 페이지의 LSN을 비교하면, Redo Log Record가 이미 반영되었는지 확인할 수 있습니다.

만약, Redo Log Record LSN이 페이지 LSN 보다 크다면 해당 Redo Log Record는 반영되지 않은 것입니다.

2. Redo Log Record 주소 탐색 비용을 최소화할 수 있습니다.

Redo Log Record 크기는 DML 종류에 따라 다릅니다. 누적 바이트 단위인 LSN을 사용하면 간단한 산술 연산만으로 Redo Log Record를 작성해야 하는 주소를 도출할 수 있습니다.
Redo Log Record를 작성해야 하는 주소 = Redo Log 시작 주소 + (LSN % 로그 그룹의 총 크기)


6. Checkpoint LSN

InnoDB는 LSN을 통해 복구 시 데이터 비교 없이 특정 Redo Log Record의 변경사항이 특정 페이지에 반영되었는지 확인할 수 있습니다.
그러나, 장애 복구 시 모든 Redo Log Record들을 읽어 일일히 비교하는 것은 비효율적입니다.

InnoDB는 장애 복구 시간을 최소화하기 위해 Checkpoint LSN을 사용합니다.
Checkpoint LSN은 Redo Log에 기록된 변경 사항 중, 완전히 반영된 마지막 지점을 나타내는 LSN입니다.
장애 복구 시, Checkpoint LSN 이전의 데이터는 이미 디스크에 반영된 것이 확실하므로 복구할 필요가 없습니다.


Page Cleaner는 주기적으로 더티 페이지들을 디스크에 쓰고, 더티 페이지들 중 가장 낮은 LSN을 Redo Log 파일 헤더의 Checkpoint LSN으로 기록합니다.

SPOF를 방지하기 위해 Checkpoint LSN은 첫 번째, 두 번째 Redo Log 파일 헤더에 기록됩니다.
복구 시, 두 Checkpoint LSN을 비교해 높은 값을 사용합니다.

또한, Checkpoint LSN을 활용하면 Redo Log 파일 순환 시 덮어쓰려는 Redo Log 파일이 Active인지 Inactive인지 판별할 수 있습니다.

OS 페이지 캐시에 반영된 LSN을 Wirte LSN이라 합니다.
Active인 경우: Redo Log Group 총 용량 ≤ Write LSN - Checkpoint LSN
Inactive인 경우: Redo Log Group 총 용량 > Write LSN - Checkpoint LSN


7. DoubleWrite Buffer

만약, 더티 페이지를 디스크에 쓰는 중 장애가 발생하면 어떻게 될까요?
16KB 페이지 하나를 디스크에 쓰는 도중 전원이 차단되면, 16KB 중 일부만 써지고 뒷부분은 예전 데이터가 남는 Partial Write 현상이 발생합니다.

Redo Log는 물리적으로 무결한 페이지의 특정 오프셋에 기록된 바이트 값을 새 값으로 덮어쓰는 방식으로 작동합니다.
페이지 쓰기 도중 장애로 인해 Torn Page 상태가 되면, Redo Log를 적용할 수 없습니다.

Torn Page란?
페이지 일부분만 저장되어 Checksum이 일치하지 않거나 헤더가 파손된 페이지를 말합니다.

Torn Page에 대해 더 궁금하다면?
https://dev.mysql.com/doc/refman/8.4/en/glossary.html#glos_torn_page

InnoDB는 이를 해결하기 위해 Doublewrite Buffer를 사용합니다.
Doublewrite Buffer는 더티 페이지들을 디스크에 쓰기 전, 해당 페이지들의 원본 전체를 기록해 두는 디스크 상의 저장 영역입니다.
장애 복구 과정에서, 특정 페이지가 Torn Page 상태라면, Redo Log를 적용하기 전 Doublewrite Buffer 파일에서 해당 페이지의 복사본을 찾아 사용합니다.

Doublewrite Buffer에 대해 더 궁금하다면?
https://dev.mysql.com/doc/refman/8.4/en/glossary.html#glos_doublewrite_buffer

마치며

이번 포스팅에서는 No-FORCE 정책을 유지하면서, 동시에 Durability를 보장하기 위한 Redo LogWAL(Write-Ahead Logging)에 대해 살펴보았습니다.

다음 포스팅에서는, InnoDB가 STEAL 정책을 유지하면서 어떻게 Atomicity를 보장할 수 있는지 알아보도록 하겠습니다.
끝까지 읽어주셔서 감사합니다.

참고 문서
https://dev.mysql.com/doc/refman/8.4/en/glossary.html
MySQL 8.4 Glossary

https://tech.kakao.com/posts/721
MySQL InnoDB Log에 대한 이해 - (1) - christy.seo, sun.j

profile
동작보단 원리를 좋아하는 머랭의 블로그입니다.

0개의 댓글