한 줄 요약: InnoDB는 WAL(Write-Ahead Logging) 원칙에 따라 데이터 페이지를 디스크에 쓰기 전에 반드시 redo log를 먼저 기록함. 커밋 시점에 redo log가 플러시되어 있으면 crash 후에도 재생(replay)으로 데이터를 복구할 수 있음. LSN은 이 redo log 스트림 위의 단조 증가 바이트 오프셋이며, Checkpoint는 LSN 이전 공간을 재활용 가능하게 만드는 진행점임.


개요

InnoDB는 변경 사항을 먼저 redo log에 기록한 뒤 나중에 데이터 파일(.ibd)에 반영함. 이 순서가 보장되는 한 crash가 발생해도 redo log를 재생하여 커밋된 트랜잭션을 복원할 수 있음.

이 글은 redo log의 내부 구조를 다음 단계로 파고듦.

  • WAL 원칙 심화: Force-Log-at-Commit 규칙과 redo record 구조
  • LSN 4종: SHOW ENGINE INNODB STATUS 에 등장하는 4개 LSN의 의미
  • 파일 구조 (8.0.30+): #innodb_redo/ 디렉터리와 #ib_redo<N> 파일 레이아웃
  • Checkpoint와 공간 재활용: Checkpoint LSN 이전 공간이 어떻게 덮어쓰여지는지
  • 튜닝: innodb_redo_log_capacity 조정과 모니터링

결론

설계 Q&A

질문답변
왜 데이터 파일보다 redo log를 먼저 쓰는가데이터 파일 페이지는 random I/O인 반면 redo log는 sequential append임. redo log를 커밋 전에 플러시해 두면 crash 후 데이터 파일이 불완전해도 replay로 복원 가능함
LSN은 무엇인가redo log 스트림의 단조 증가 바이트 오프셋(byte offset). 모든 수정은 LSN 범위로 표현되며, 어디까지 기록·플러시·체크포인트됐는지 추적하는 기준선임
Checkpoint는 왜 필요한가redo log 파일은 순환 구조임. Checkpoint LSN 이전 데이터는 이미 데이터 파일에 반영되었으므로 그 공간을 덮어쓸 수 있음. Checkpoint가 오래 멈추면 redo log가 꽉 차고 쓰기가 지연됨
8.0.30 이전 설정 변수는 무엇인가innodb_log_file_size × innodb_log_files_in_group. 8.0.30+에서 innodb_redo_log_capacity 단일 변수로 통합됨. 두 구형 변수는 deprecated 상태임

운영 시 기억할 것

  • innodb_redo_log_capacity 가 너무 작으면 Checkpoint 압박이 잦아져 Page Cleaner I/O 급등 → 전체 TPS 저하로 이어짐
  • SHOW ENGINE INNODB STATUS LOG 섹션의 Log sequence numberLast checkpoint at 차이가 크면 Checkpoint가 따라가지 못하고 있다는 신호임
  • Innodb_redo_log_capacity_resized status variable로 동적 변경이 실제로 반영됐는지 확인 가능함
  • redo log를 비활성화(ALTER INSTANCE DISABLE INNODB REDO_LOG)하는 것은 초기 bulk load 전용임. 운영 중 사용 금지임

상세

1. WAL 원칙: Force-Log-at-Commit

WAL(Write-Ahead Logging)의 핵심 규칙은 “데이터 페이지를 디스크에 쓰기 전에 redo log를 먼저 써야 한다” 는 것임.

InnoDB는 이 규칙을 Force-Log-at-Commit 형태로 구현함. COMMIT 이 완료되려면 그 트랜잭션의 모든 redo log 레코드가 디스크에 플러시되어야 함(innodb_flush_log_at_trx_commit=1 기준).

graph LR
    A["트랜잭션 변경<br/>(Buffer Pool dirty page)"] --> B["Log Buffer에<br/>redo record 기록"]
    B --> C["COMMIT 호출"]
    C --> D["Log Buffer → redo log 파일<br/>플러시 (fsync)"]
    D --> E["COMMIT 완료 반환"]
    E --> F["나중에 Page Cleaner가<br/>dirty page → .ibd 플러시"]

이 순서 덕분에 Page Cleaner가 dirty page를 .ibd 데이터 파일에 플러시하기 전에 crash가 발생해도, 이미 디스크에 플러시된 redo log를 replay하여 커밋된 변경을 데이터 파일에 다시 적용할 수 있음.

redo log record 구조

하나의 redo log record는 대략 다음 필드로 구성됨.

필드설명
type변경 유형 (페이지 수정 종류)
space_id테이블스페이스 ID
page_no페이지 번호
LSN이 레코드가 기록된 시점의 LSN
data실제 변경 내용 (before/after 바이트)

space_id는 어떤 tablespace 파일인지, page_no는 그 파일 안의 몇 번째 page인지를 가리킴. crash recovery 때 InnoDB는 redo record의 space_id + page_no를 보고 “이 변경을 어느 .ibd 파일의 어느 page에 다시 적용해야 하는지”를 찾음.


2. LSN (Log Sequence Number)

LSN은 redo log 스트림 전체를 통틀어 단조 증가하는 바이트 오프셋임. 파일 로테이션이 일어나도 LSN은 계속 증가하며 절대 감소하지 않음.

4가지 LSN

SHOW ENGINE INNODB STATUS LOG 섹션에 등장하는 4개 LSN은 각각 다른 진행 단계를 나타냄.

LSN 이름의미
Log sequence number현재까지 생성된 redo log의 끝 지점. 가장 최신 값임
Log flushed up to이 LSN까지 redo log 파일에 플러시(fsync)됨
Pages flushed up to이 LSN 이전의 dirty page가 .ibd 파일에 반영됨
Last checkpoint at이 LSN까지 Checkpoint가 완료됨. 이 이전 redo 공간은 재사용 가능함

정상 상태에서는 네 값이 거의 같거나 아주 좁은 차이를 유지함. 쓰기가 급증하거나 innodb_redo_log_capacity 가 작으면 Log sequence numberLast checkpoint at 사이 격차가 벌어짐.

graph LR
    LSN["Log sequence number<br/>(최신 생성)"]
    FLU["Log flushed up to<br/>(디스크 플러시)"]
    PFU["Pages flushed up to<br/>(데이터 파일 반영)"]
    CKP["Last checkpoint at<br/>(공간 재활용 경계)"]

    LSN --> FLU --> PFU --> CKP

3. redo log 파일 구조 (8.0.30+)

MySQL 8.0.30부터 redo log 파일은 데이터 디렉터리 안의 #innodb_redo/ 서브디렉터리에 위치함.

파일 명명 규칙

  • #ib_redo<N> — 현재 사용 중인 파일 (ordinary)
  • #ib_redo<N>_tmp — 대기 중인 예비 파일 (spare)

InnoDB는 총 32개 파일을 유지하며, 각 파일 크기는 innodb_redo_log_capacity / 32 임.

기본값(innodb_redo_log_capacity = 104857600)에서는 파일 1개당 104857600 / 32 = 3276800 bytes ≈ 3.1 MiB.

Performance Schema로 파일 범위 조회

SELECT FILE_ID, START_LSN, END_LSN, SIZE_IN_BYTES, IS_FULL
FROM performance_schema.innodb_redo_log_files;
+---------+-----------+-----------+--------------+---------+
| FILE_ID | START_LSN | END_LSN   | SIZE_IN_BYTES| IS_FULL |
+---------+-----------+-----------+--------------+---------+
|       9 |  29480960 |  32755712 |      3276800 |       0 |
+---------+-----------+-----------+--------------+---------+

MySQL 8.4.8 에서 직접 확인

각 파일이 커버하는 LSN 범위(START_LSN ~ END_LSN)가 명시됨. 파일이 가득 차면(IS_FULL=1) 다음 spare 파일이 ordinary로 전환됨.


4. Checkpoint와 redo log 공간 관리

redo log 파일은 물리적으로는 순환 구조(circular)이지만, 이해할 때는 LSN이 계속 증가하는 일직선 로그로 펼쳐서 보는 편이 쉬움. 핵심은 두 지점임.

  • Log sequence number: 지금까지 생성된 redo log의 끝 지점, 즉 새 redo record가 계속 붙는 위치임
  • Last checkpoint at: 이 지점 이전 redo log는 crash recovery에 더 이상 필요 없다고 표시된 경계임

Log sequence number - Last checkpoint at 차이는 redo log에서 아직 재사용할 수 없는 구간의 크기임. 이 차이가 커질수록 redo log 여유 공간이 줄어들고, 차이가 innodb_redo_log_capacity에 가까워지면 새 redo record를 쓸 공간이 부족해짐.

이때 InnoDB는 dirty page를 데이터 파일에 더 빨리 플러시하고 checkpoint를 전진시켜 오래된 redo 구간을 재사용 가능하게 만들어야 함. 이 과정이 따라가지 못하면 사용자 트랜잭션의 쓰기가 지연될 수 있음.

Checkpoint LSN이 전진하지 못하는 상황은 다음에서 발생함.

  • dirty page가 빠르게 생성되지만 Page Cleaner의 .ibd 플러시가 따라가지 못할 때
  • innodb_redo_log_capacity 가 workload 대비 너무 작을 때

redo log가 꽉 차면 InnoDB는 새 변경을 받기 전에 강제 Checkpoint를 실행함. 이 구간에 쓰기 지연이 발생함.

Adaptive Flushing으로 예방

InnoDB의 Adaptive Flushing은 Log sequence number - Last checkpoint at 비율을 감시하여, redo log 여유 공간이 줄어들수록 Page Cleaner의 플러시 속도를 높임. 이를 통해 급격한 Checkpoint 강제 실행을 예방함.


5. innodb_redo_log_capacity 튜닝

주요 속성

항목
도입 버전MySQL 8.0.30
기본값104857600 bytes (100 MiB)
단위bytes
DynamicYes (SET GLOBAL 가능)
대체한 변수innodb_log_file_size, innodb_log_files_in_group (deprecated)

8.0.30 이전 설정 방식과의 비교

구분8.0.30 이전8.0.30+
설정 변수innodb_log_file_size × innodb_log_files_in_groupinnodb_redo_log_capacity (단일)
동적 변경불가 (재시작 필요)SET GLOBAL 로 즉시 적용
파일 위치데이터 디렉터리 직하 ib_logfile0, ib_logfile1#innodb_redo/#ib_redo<N>

권장 용량 기준

  • innodb_redo_log_capacity 가 너무 작으면 Checkpoint 압박 → Page Cleaner I/O 급등 → 전체 쓰기 TPS 저하
  • 일반 OLTP: 기본값 100 MiB도 동작하나, write-heavy 워크로드라면 1~4 GiB 권장
  • SET GLOBAL innodb_redo_log_capacity = 2147483648; (2 GiB 예시)

동적 변경 확인

변경 후 Innodb_redo_log_capacity_resized status variable 값이 새 설정과 일치하면 전환 완료임.

SET GLOBAL innodb_redo_log_capacity = 2147483648;
SHOW STATUS LIKE 'Innodb_redo_log_capacity_resized';
+----------------------------------+------------+
| Variable_name                    | Value      |
+----------------------------------+------------+
| Innodb_redo_log_capacity_resized | 2147483648 |
+----------------------------------+------------+

테스트

MySQL 8.4.8 에서 직접 확인함.

1. SHOW ENGINE INNODB STATUS — LOG 섹션

SHOW ENGINE INNODB STATUS\G
LOG
---
Log capacity                 104857600
Log capacity used            104857600
Log sequence number          29672233
Log buffer assigned up to    29672233
Log buffer completed up to   29672233
Log written up to            29672233
Log flushed up to            29672233
Added dirty pages up to      29672233
Pages flushed up to          29672233
Last checkpoint at           29672233
Log minimum file id is       9
Log maximum file id is       9
35 log i/o's done, 5.00 log i/o's/second

MySQL 8.4.8 에서 직접 확인

새 인스턴스라 모든 LSN이 동일한 값(29672233)으로 수렴해 있음. Log sequence numberLast checkpoint at 이 같다는 것은 Checkpoint가 최신 상태임을 의미함.

2. 기본값 확인

SELECT @@innodb_redo_log_capacity, @@innodb_flush_log_at_trx_commit;
+----------------------------+----------------------------------+
| @@innodb_redo_log_capacity | @@innodb_flush_log_at_trx_commit |
+----------------------------+----------------------------------+
|                  104857600 |                                1 |
+----------------------------+----------------------------------+

MySQL 8.4.8 에서 직접 확인

innodb_redo_log_capacity 기본값 100 MiB, innodb_flush_log_at_trx_commit 기본값 1(매 커밋마다 fsync) 확인됨.

3. Innodb_redo 상태 변수

SHOW STATUS LIKE 'Innodb_redo%';
+----------------------------------+-----------+
| Variable_name                    | Value     |
+----------------------------------+-----------+
| Innodb_redo_log_read_only        | OFF       |
| Innodb_redo_log_uuid             | 553266500 |
| Innodb_redo_log_checkpoint_lsn   | 29672233  |
| Innodb_redo_log_current_lsn      | 29672233  |
| Innodb_redo_log_flushed_to_disk_lsn | 29672233 |
| Innodb_redo_log_logical_size     | 512       |
| Innodb_redo_log_physical_size    | 3276800   |
| Innodb_redo_log_capacity_resized | 104857600 |
| Innodb_redo_log_resize_status    | OK        |
| Innodb_redo_log_enabled          | ON        |
+----------------------------------+-----------+

MySQL 8.4.8 에서 직접 확인

Innodb_redo_log_physical_size 가 3276800 bytes = 3.1 MiB로 파일 1개 크기와 일치함(104857600 / 32 = 3276800). logical_size 512 bytes는 실제 사용 중인 redo 데이터 크기임.

4. #innodb_redo/ 디렉터리 구조

docker exec blog-test-mysql-8.4-redo ls -la /var/lib/mysql/#innodb_redo/
total 102408
-rw-r----- 1 mysql mysql 3276800 May 27 09:31 #ib_redo9
-rw-r----- 1 mysql mysql 3276800 May 27 09:31 #ib_redo10_tmp
-rw-r----- 1 mysql mysql 3276800 May 27 09:31 #ib_redo11_tmp
...
-rw-r----- 1 mysql mysql 3276800 May 27 09:31 #ib_redo40_tmp
drwxr-x--- 2 mysql mysql    4096 May 27 09:31 .
drwxrwxrwt 8 mysql mysql    4096 May 27 09:31 ..

MySQL 8.4.8 에서 직접 확인

#ib_redo9 1개가 ordinary(활성), 나머지 31개가 _tmp suffix의 spare 파일임. 총 32개 × 3.1 MiB = 100 MiB로 innodb_redo_log_capacity 와 일치함.


참고