목차
1. Redo Log
Redo 로그는 예기치 않은 종료로 인해 완료되지 않은 트랜잭션이 작성한 데이터를 수정하기 위해 충돌 복구 중에 사용되는 디스크 기반 데이터 구조 입니다.
리두 로그는 ACID 중 지속성(Durability)를 보장하기 위해 사용됩니다.
트랜잭션이 커밋되었다면 시스템이 꺼지더라도 그 결과는 반드시 디스크에 반영되어야 하므로, 이를 위해 디스크 쓰기 작업 전 리두 로그에 변경사항을 먼저 기록합니다.
리두 로그는 디스크에 물리적인 파일로 저장됩니다(#ib_redoN).
리두 로그 파일에 기록된 데이터는 영향을 받은 레코드 단위로 인코딩되며, 이 데이터 전체를 리두라고 부릅니다.
리두 로그 파일을 통해 데이터가 흘러가는 과정은 계속 증가하는 LSN(Log Sequence Number) 값에 의해 표현됩니다.
데이터 수정이 발생함에 따라 리두 로그 데이터가 추가되며, 체크 포인트가 진행됨에 따라 가장 오래된 데이터는 제거됩니다.
2. Redo Log 구성 요소
MySQL :: MySQL 8.0: New Lock free, scalable WAL design
위 이미지는 MySQL8.0의 Redo Log 구조를 이미지화 한 것 입니다.
MySQL5.7 까지는 단일 쓰기 mutex(log_sys_t::mutex)로 직렬화 되어있었으나, MySQL8.0부터는 Lock-free slot 구조로 변경되어 multi-producer 를 지원합니다.
mutex
mutual exclusion.
동시에 여러 스레드가 하나의 공유 자원 (로그버퍼, 페이지 캐시 등)에 접근하지 못하도록 제어하는 동기화 기법입니다.
한 번에 하나의 스레드만 자원에 접근할 수 있도록 하는 잠금장치입니다.
log_sys_t::mutex
InnoDB에서는 리두 로그에 쓰기작업을 할 때 여러 트랜잭션이 동시에 로그 버퍼를 수정할 수 없도록 log_sys_t::mutex라는 mutex를 사용해 직렬(순차적)접근만 가능하도록 제어합니다.
multi-producer
동시에 여러 스레드나 트랜잭션이 로그 버퍼에 데이터를 쓸 수 있는 구조를 의미합니다.
MySQL 8.0기준 리두 로그의 구성 요소는 아래와 같습니다.
구성 요소 | 설명 |
Log Buffer | 로그가 메모리에 임시 저장되는 영역. innodb_log_buffer_size 시스템 변수로 크기 제어 가능. |
Slot Buffer | 병렬 로그 작성을 위한 구조화된 버퍼. 순환구조로 구현된 slot array를 통해 다중 쓰레드 쓰기 최적화. |
Redo Log Files | 디스크에 저장되는 로그 파일. innodb_redo_log_capacity 시스템 변수로 크기 제어 가능. |
Log Flusher | 버퍼의 내용을 디스크로 flush하는 백그라운드 쓰레드. log_writer와 log_flusher 백그라운드 쓰레드가 분리되어 성능 향상 |
Checkpoint | flush 완료 지점 추적 및 복구 효율성 제공. LSN 기반 checkpoint 유지. crash recovery 효율화. |
LSN | 로그 위치를 추적하는 논리 번호 |
1) Log Buffer
위 녹색 영역에 있는 부분은 로그 버퍼의 전체 영역을 표현한 것입니다.
InnoDB의 로그 버퍼는 오직 리두 로그 레코드만 저장하는 공간으로, 리두 로그 외의 다른 구성요소는 포함되지 않습니다.
참고로 위 이미지는 로그 버퍼의 내부 상태 및 쓰기 진행 상황일 뿐, 리두 로그 파일을 표현한 것이 아님에 유의해야 합니다.
구성 요소 | 기능 |
write_lsn | 현재까지 로그 버퍼에 기록이 완료된 위치 |
buf_ready_for_write_lsn | 중간에 구멍(hole)없이 연속적으로 안전하게 디스크에 flush 가능한 위치 |
current_lsn | 새로운 미니 트랜잭션이 로그 버퍼에 기록될 다음 위치 |
참고로 write_lsn, buf_ready_for_write_lsn, current_lsn 메모리 상의 값을 가진 변수로 구현되지만, 의미상으로는 로그 상의 특정 위치를 가리키는 포인터 역할을 합니다
미니 트랜잭션
미니 트랜잭션은 InnoDB 내부에서 처리하는 아주 작은 단위의 트랜잭션 작업을 의미합니다.
일반적인 사용자 트랜잭션과는 달리 미니 트랜잭션은 디스크 I/O 동기화, 로그 기록, 버퍼 풀 페이지 수정 등 원자적 작업 단위를 의미합니다.
미니 트랜잭션은 InnoDB 가 내부적으로 데이터 무결성과 일관성을 보장하기 위해 존재합니다.
InnoDB 는 장애 복구를 위해 데이터 변경 내용을 로그에 반드시 기록하는데, 이때 한 번에 너무 큰 단위로 처리하면 복구 시 작업 단위가 커지므로 미니 트랜잭션이 되도록 작업 단위를 작게 쪼갭니다.
구멍 (Hole)
구멍(Hole)은 로그 버퍼 내에서 할당된 LSN 순서 중 연속되지 않은 빈 공간을 의미합니다.
예를 들어 LSN이 1000, 1001, 1003이 기록되어있고 1002가 아직 기록되어있지 않았다면 '1002위치에 구멍이 있다'고 표현합니다.
로그는 반드시 순서대로 기록되고 플러시되므로 1002에구멍이 있다면 buf_ready_for_write_lsn은 1001까지만 플러시하고 1002가 기록될때까지 1003의 플러시를 보류합니다.
이는 장애 복구의 일관성을 위한 구조입니다.
로그를 순서대로 기록하고 플러시해야 복구 시점에서 어떤 트랜잭션이 완전히 적용되었는지 판단할 수 있습니다.
1_ write_lsn
write_lsn은 실제 로그를 디스크에 작성한 마지막 지점을 의미합니다.
log_writer가 buf_ready_for_write_lsn 까지의 로그를 디스크로 쓰고난 후 실제 쓰기작업이 완료된 위치가 write_lsn의 값이 됩니다.
이 위치까지의 로그는 디스크(혹은 OS 페이지 캐시)에 존재하며, 장애 복구 작업 시 사용됩니다.
즉, write_lsn의 값은 '이 위치까지의 변경사항은 이미 디스크에 적용 되어있음'을 의미합니다.
2_ buf_ready_for_write_lsn
buf_ready_for_write_lsn은 로그 버퍼에서 구멍(hole)없이 연속적으로 미니 트랜잭션이 기록된 마지막 지점을 의미합니다.
해당 지점까지가 플러시 대상으로 확정된 영역이 됩니다.
log_writer 스레드는 buf_ready_for_write_lsn까지를 페이지 캐시에 기록 가능하다고 판단합니다.
장애복구 시 재생 가능한 지점이 buf_ready_for_write_lsn으로 한정됩니다.
3_ current_lsn
current_lsn은 다음에 로그가 기록될 위치를 의미합니다.
로그를 기록하는 mini transaction마다, current_lsn값인 위치를 기반으로 자기 로그의 공간을 예약합니다.
다중 스레드 환경에서 트랜잭션은 로그를 기록하기 전 current_lsn을 원자적으로 증가시켜 공간을 할당받습니다.
예를들어 스레드 A가 1200~1300 까지의 영역을 예약한다면 current_lsn은 1231이 되고, 이후 스레드 B가 1231~1245를 예약하면 current_lsn은 1246이 됩니다.
current_lsn은 로그 버퍼에서 병렬 미니 트랜잭션의 로깅 위치 충돌을 방지합니다.
또한 장애복구 시 리두로그가 어디까지 있었는지를 파악할 수 있는 기준이 됩니다.
2) Slot Buffer
로그 버퍼는 여러 쓰레드가 동시에 데이터를 기록하는 공간이므로 모든 쓰레드가 동시에 버퍼에 쓰기 작업을 진행하려면 성능 저하가 발생합니다.
따라서 로그 버퍼의 기록 상태를 관리하기 위해 슬롯을 이용합니다.
슬롯에는 '로그 버퍼에 어떤 트랜잭션이 어느 부분까지 기록을 했는지'에 대한 정보가 저장됩니다.
*M: 모든 이전 LSN에 대한 로그 버퍼 쓰기가 안전하게 완료된 최대 LSN. (=체크포인트 LSN)
디스크 쓰기가 아닌 '로그 버퍼 쓰기'입니다.
슬롯 배열은 고정된 크기를 가집니다.
또한 배열의 끝에 도달한 경우 다시 처음 위치로 되돌아가 이전 슬롯을 재사용하는 순환 버퍼 구조로 구현되어 있습니다.
순환 버퍼 구조는 리두 로그의 시간이 지남에 따라 과거의 슬롯 정보가 더 이상 필요하지 않게 되는 특성과 일치합니다.
또한 불필요한 메모리 소비를 방지하며 슬롯의 재사용하여 시스템 자원을 효율적으로 관리할 수 있습니다.
결과적으로 슬롯 배열을 순환 버퍼 구조로 구현하여 로그 버퍼 내의 상태 관리 구조를 단순화시키고, 메모리 사용의 효율성을 높일 수 있습니다.
또한 InnoDB는 순환 버퍼 내 로그 처리의 정합성과 병렬성을 보장하기 위해 recent_written과 recent_closed라는 두 개의 상태 추적 인스턴스를 이용합니다.
recent_closed는 dirty 페이지 추가 작업의 동시 실행을 추적하며, LSN이 더 작은 작업들이 모두 완료된 시점을 기준으로 최대 LSN(M)을 갱신합니다.
미니 트랜잭션이 커밋될 때는 로그 레코드를 로그 버퍼에 복사하고 recent_written에 완료를 보고합니다.
이후 M과의 차이가 너무 크지 않은 경우에만 더티 페이지를 플러시 리스트에 추가합니다.
그리고 그 작업이 끝나면 recent_closed에 보고합니다.
이런 식으로 순서를 완전히 지키진 않지만 플러시 리스트 내부의 순서는 제한된 범위 내에서만 어긋나므로, 플러시 및 체크포인트 제약은 여전히 만족됩니다.
1_ recent_written
recent_written은 로그 버퍼에 기록이 완료된 LSN을 추적합니다.
충돌 복구 시 이 값을 기준으로 어디까지 로그에 기록되었는지 판단할 수 있습니다.
해당 슬롯들은 스레드가 순환하는동안 읽기/쓰기 시 적절한 순서를 보장합니다.
2_ recent_closed
recent_closed는 flush_order_mutex 가 제거되면서 발생할 수 있는 문제를 해결하기 위해 도입되었습니다.
recent_closed는 아래 두가지 제약 조건을 보장합니다.
- 체크포인트
LSN = L2에서 fuzzy 체크포인트를 쓸 수 없는데, 만약 L1 < L2인 dirty 페이지가 존재한다면 L1에 해당하는 내용이 디스크에 반영되지 않았으나, 복구는 L2부터 시작하게 되므로 문제가 됩니다.
recent_closed는 dirty 페이지가 flush 리스트에 추가되기 전에 일정 범위 내에서 순서를 조절해서, 체크포인트 LSN보다 더 작은 LSN을 가진 dirty 페이지들이 반드시 디스크에 기록되도록 보장합니다.
따라서 복구시 L2 부터 시작되더라도 누락된 데이터가 없게끔 합니다. - 플러시 순서
플러시는 항상 플러시 리스트 내에서 가장 오래된 페이지 부터 처리해야합니다.
recent_closed는 dirty page들이 flush 리스트에 들어가는 순서를 LSN 기준으로 제한된 범위(L) 내에서만 어그러지게 하도록 조절합니다.
이를 통해 flush 리스트 내에서 오래된 페이지부터 처리하는 정책이 유지되도록 돕습니다.
MySQL: Redo log buffer
flush_order_mutex
flush_order_mutex는 리두 로그 처리과정에서 변경된 페이지들을 flush list에 순서대로 추가하기 위해 사용되던 뮤텍스(락)입니다.
flush list에 dirty page를 추가할 때 사용하는 락으로 볼 수 있습니다.
MySQL 8.0이전 버전까지 사용되었으며 8.0에서 제거되었습니다.
Fuzzy Checkpoint
모든 dirty page를 디스크에 즉시 반영하지않고, 일부 dirty page만 플러시하면서 찍는 체크포인트입니다.
Fuzzy Checkpoint를 사용하여 dirty page를 조금씩, 백그라운드에서 순차적으로 flush할 수 있습니다.
Fuzzy Checkpoint는 모든 변경내용을 디스크에 flush 할 경우 발생할 수 있는 디스크 I/O를 최소화 하기 위해 도입되었습니다.
3) Log Flusher
Log Flusher는 log_writer와 log_flusher 두 가지 스레드를 포함합니다.
Log Flusher는 LogBuffer와 RedoLogFiles 사이에서 동작하는 주체(thread)로, 리두 로그에서 구조적인 구성요소는 아닙니다.
따라서 종속 구조 상에는 포함되지 않지만 작동 흐름 상 핵심 중간자 역할을 수행합니다.
두 스레드는 MySQL 8.0에서 도입된것으로 이전에는 사용자 스레드(user thread)가 트랜잭션 커밋+log write+ log flush 작업까지 모두 수행하였습니다.
하나의 스레드(사용자 스레드)가 너무 많은 역할을 수행하여 성능 병목과 지연(latency)가 발생할 수 있어 이를 보완하기 위해 log_writer와 log_flusher를 도입하여 역할을 분담하였습니다.
log_writer와 log_flusher 두 스레드는 함께 동작하며 로그의 안정성을 보장합니다.
log_writer는 로그를 디스크의 OS 캐시에 기록하는 역할, log_flusher는 해당 로그를 실제 디스크에 반영하는 역할을 수행합니다.
두 스레드는 기능적으로 분리되어 있으나 리두 로그 관리 체계에서 상호 보완적으로 작동합니다.
또한 트랜잭션의 지속성(Durability)을 보장하는 데 핵심적인 역할을 담당합니다.
1_ log_writer
log_writer는 로그 버퍼를 OS 페이지 캐시에 기록하는 역할을 합니다.
이 때 가능한 한 전체 블록 단위로 기록하며 불완전한 블록은 건너뛰고, 기록이 끝나면 write_lsn을 갱신합니다.
log_writer는 메모리에 위치한 로그 버퍼 (정확히는 슬롯 버퍼와 recent_written 구조를 포함)에 쌓인 리두 로그 레코드를 디스크의 OS 파일 버퍼에 write() 시스템 호출을 통해 기록합니다.
이는 실제 물리 디스크까지 기록되지는 않으나, 운영체제의 파일 캐시에 로그를 반영하여 성능과 안전성을 향상시키는 역할을 합니다.
log_writer는 다음과 같은 조건에서 활성화됩니다.
- 트랜잭션이 커밋된 경우
- 로그 버퍼가 특정 임계치를 초과하는 경우
- 시스템 설정에 따라 주기적으로 (innodb_flush_log_at_timeout 시스템 변수로 제어)
2_ log_flusher
log_flusher는 log_writer가 OS 파일 버퍼에 기록한 로그를 실제 디스크에 영구적으로 반영하기 위해 fsync() 시스템 호출을 수행합니다.
log_flusher는 write_lsn을 읽고 fsync()를 호출하여 flushed_to_disk_lsn을 갱신합니다.
이 단계가 완료되어야만 로그가 진정한 의미에서 영속성(durability)을 갖추었다고 볼 수 있습니다.
log_flusher는 다음과 같은 경우에 활성화됩니다.
- 일정 시간 간격으로 주기적 flush를 수행해야 하는 경우
- Redo 로그 공간이 포화되어 Checkpoint가 필요할 때
- 플러시 작업이 완료되면 flushed_to_disk_lsn이라는 내부 상태 변수가 갱신되며, 이 값은 시스템 복구 시점이나 체크포인트 진행 시 중요한 기준점으로 활용됩니다.
3. Redo Log Flush 프로세스
위 다이어그램은 리두 로그 처리흐름을 표현한 것입니다.
- 변경작업을 먼저 Log Buffer(로그 버퍼) 에 기록합니다
-> 만일 flush 속도가 느려 M이 너무 과거라면 대기합니다. - 로그 버퍼는 LSN 단위로 기록되며 일정 주기나 조건에 따라 디스크로 플러시 됩니다.
- 디스크에 플러시할 더티 페이지는 FLUSH LIST 0~2에 분산되어 관리됩니다.
- 각 FLUSH LIST는 더티 페이지를 디스크에 반영합니다.
- 디스크 반영이 완료되면 해당 페이지가 커버하는 start_lsn ~ end_lsn 범위를 새 데이터 구조에 기록하여 recovery 및 checkpoint 에 사용됩니다.
MySQL 8.0부터는 위와 같은 처리흐름을 통해 트랜잭션 무결성과 복구를 구현합니다.
또한 비동기 디스크 쓰기 구조를 사용하면서도 데이터 손실을 방지합니다.
4. OLD REDO vs NEW REDO
공식 블로그에 기재된 새로운 redo log가 도입되기 전 버전과 그 직후 버전 간의 간단한 비교를 살펴보겠습니다.
sysbench의 oltp_update_nokey 테스트를 사용하며, 테스트 환경은 다음과 같습니다.
테이블 : 8개의 테이블 (각각 레코드 천만건)
설정: innodb_flush_log_at_trx_commit = 1
:: 참고문헌
MySQL :: MySQL 8.0: New Lock free, scalable WAL design
MySQL :: MySQL 8.0: New Lock free, scalable WAL design
The Write Ahead Log (WAL) is one of the most important components of a database. All the changes to data files are logged in the WAL (called the redo log in InnoDB). This allows to postpone the moment when the modified pages are flushed to disk, still prot
dev.mysql.com
DimitriK's (dim) Weblog : MySQL Performance : 8.0 and Sysbench OLTP_RW / Update-NoKEY
DimitriK's (dim) Weblog : MySQL Performance : 8.0 and Sysbench OLTP_RW / Update-NoKEY
This post is following previously published OLTP_RO results for MySQL 8.0 (latin1 and utf8mb4 charsets), and now is focusing on Sysbench RW workloads, particularly "mixed" OLTP_RW and Update-NoKey : OLTP_RW : while this workload has writes, it's mainly dri
dimitrik.free.fr
MySQL: Redo log buffer
When mtr commits, data has to be moved from internal buffer of the mtr to the redo log buffer. For a better concurrency, procedure for writing to the log buffer consists of following steps: Reservation of space in the redo Copying data to the reserved spac
dev.mysql.com
'Database > MySQL' 카테고리의 다른 글
[MySQL] 에러로그 모니터링 (log_error_services) (0) | 2025.07.22 |
---|---|
[MySQL] Error 1300: Invalid utf8mb4 character string: '' 에러 해결법 (0) | 2025.05.23 |
[MySQL] Change Buffer 체인지 버퍼 (0) | 2025.04.10 |
[MySQL] Undo Log & Undo Tablespace (0) | 2025.04.09 |
[MySQL] InnoDB Cluster 3부작 : 3. MySQL InnoDB Cluster Metadata 생성 및 MySQL Router 구성하기 (0) | 2025.04.01 |