Redo Log 파일 크기와 checkpoint age: 쓰기 성능과 복구 시간의 균형
MySQL InnoDB Redo Log 크기와 checkpoint age가 쓰기 성능, crash recovery 시간, 운영 안정성에 미치는 영향을 정리한다.
1. 왜 Redo Log 크기와 checkpoint age를 함께 보아야 하는가
InnoDB에서 redo log는 데이터 페이지 변경 내용을 먼저 순차 기록하여 장애 후 복구를 가능하게 하는 핵심 구조다. 사용자가 INSERT, UPDATE, DELETE를 실행하면 변경된 데이터 페이지가 즉시 테이블스페이스에 동기적으로 모두 기록되는 것이 아니라, 먼저 메모리의 buffer pool과 redo log에 반영된다. 이 방식은 랜덤 I/O를 줄이고 쓰기 처리량을 높이지만, 동시에 “아직 데이터 파일에 반영되지 않은 변경분”이라는 운영상 부채를 만든다.
이 부채의 크기를 이해할 때 자주 사용하는 개념이 checkpoint age다. 엄밀히 말하면 checkpoint age는 현재 로그 시퀀스 번호(Log sequence number, LSN)와 마지막 checkpoint LSN 사이의 차이다. 값이 커질수록 InnoDB가 장애 복구 시 다시 적용해야 할 redo 양이 많다는 뜻이며, 동시에 dirty page가 충분히 빨리 flush되지 못하고 있다는 신호일 수 있다.
Redo Log 파일 크기를 크게 잡으면 쓰기 폭주를 흡수할 여유가 커진다. 그러나 checkpoint age가 커질 수 있는 상한도 커지므로 crash recovery 시간이 길어질 수 있다. 반대로 Redo Log가 너무 작으면 복구 대상은 작아질 수 있지만, checkpoint를 자주 밀어야 하므로 foreground 쓰기 작업이 page flushing 압박을 받을 수 있다. 따라서 운영자는 “Redo Log는 클수록 좋다” 또는 “작을수록 안전하다”라는 단순 기준이 아니라, 워크로드의 쓰기 패턴, 디스크 성능, 장애 복구 목표 시간, Aurora MySQL과 Community MySQL의 저장소 차이를 함께 고려해야 한다.
2. InnoDB 쓰기 경로와 checkpoint의 위치
InnoDB 쓰기 경로는 크게 네 단계로 이해할 수 있다.
- 트랜잭션이 buffer pool의 데이터 페이지를 변경한다.
- 변경 내용에 대한 redo record가 redo log buffer에 만들어진다.
- commit 정책과
innodb_flush_log_at_trx_commit값에 따라 redo log가 디스크로 flush된다. - dirty page는 즉시가 아니라 백그라운드 flushing 또는 checkpoint 압력에 따라 데이터 파일로 기록된다.
다음 그림은 Redo Log와 checkpoint age의 관계를 단순화한 것이다.
flowchart LR
A[사용자 트랜잭션] --> B[Buffer Pool의 데이터 페이지 변경]
B --> C[Redo Log Buffer에 redo record 생성]
C --> D[Redo Log File에 순차 기록]
B --> E[Dirty Page]
E --> F[Page Cleaner가 데이터 파일로 flush]
D --> G[Current LSN]
F --> H[Checkpoint LSN 진행]
G -. 차이 .-> I[Checkpoint Age]
H -. 차이 .-> I
Checkpoint는 “이 LSN 이전의 변경분은 데이터 파일에 안전하게 반영되었으므로 crash recovery 때 redo를 다시 적용하지 않아도 된다”는 경계다. checkpoint LSN이 앞으로 잘 진행되면 checkpoint age는 낮게 유지된다. 반대로 쓰기량이 page flushing 능력보다 빠르면 current LSN은 빠르게 증가하지만 checkpoint LSN은 늦게 따라오므로 checkpoint age가 커진다.
운영 관점에서 중요한 점은 redo log file size가 단순한 저장 공간 설정이 아니라 InnoDB 내부 스케줄링의 압력 조건이라는 것이다. Redo Log 공간이 부족해지면 InnoDB는 더 이상 여유롭게 dirty page를 쌓아둘 수 없고, checkpoint를 강제로 전진시키기 위해 flushing을 공격적으로 수행한다. 이때 사용자 쓰기 지연, commit latency 증가, log free check 대기, I/O 포화가 나타날 수 있다.
3. MySQL 8.0 이후 Redo Log 크기 설정의 변화
MySQL 5.7과 8.0 초기 운영 경험에서는 innodb_log_file_size와 innodb_log_files_in_group를 곱해 전체 redo capacity를 계산하는 방식이 익숙했다. MySQL 8.0.30부터는 동적 설정 가능한 innodb_redo_log_capacity가 도입되어 전체 redo log 용량을 더 직접적으로 관리할 수 있다. 운영 문서나 오래된 점검 스크립트가 아직 innodb_log_file_size만 기준으로 경고한다면 대상 버전을 반드시 확인해야 한다.
아래 쿼리는 현재 서버에서 Redo Log 용량 관련 변수를 확인하는 기본 점검 예제다.
SELECT VERSION() AS mysql_version;
SHOW VARIABLES WHERE Variable_name IN (
'innodb_redo_log_capacity',
'innodb_log_file_size',
'innodb_log_files_in_group',
'innodb_flush_log_at_trx_commit',
'innodb_flush_method',
'innodb_io_capacity',
'innodb_io_capacity_max'
);
실행 결과(MySQL 8.0.46):
mysql> SELECT VERSION() AS mysql_version;
+---------------+
| mysql_version |
+---------------+
| 8.0.46 |
+---------------+
1 row in set (0.00 sec)
mysql> SHOW VARIABLES WHERE Variable_name IN (
-> 'innodb_redo_log_capacity',
-> 'innodb_log_file_size',
-> 'innodb_log_files_in_group',
-> 'innodb_flush_log_at_trx_commit',
-> 'innodb_flush_method',
-> 'innodb_io_capacity',
-> 'innodb_io_capacity_max'
-> );
+--------------------------------+-----------+
| Variable_name | Value |
+--------------------------------+-----------+
| innodb_flush_log_at_trx_commit | 1 |
| innodb_flush_method | fsync |
| innodb_io_capacity | 200 |
| innodb_io_capacity_max | 2000 |
| innodb_log_file_size | 50331648 |
| innodb_log_files_in_group | 2 |
| innodb_redo_log_capacity | 104857600 |
+--------------------------------+-----------+
7 rows in set (0.01 sec)
MySQL 8.0.30 이상에서는 innodb_redo_log_capacity가 핵심 기준이다. 그 이전 버전에서는 innodb_log_file_size * innodb_log_files_in_group 방식으로 전체 용량을 추정한다. 단, 이 글의 SQL 예제는 MySQL 8.0 테스트 컨테이너에서 객체 존재와 실행 가능성을 기준으로 검증한다. 실제 운영 서버에서는 minor version, 배포판 빌드, 관리형 서비스의 파라미터 노출 정책에 따라 표시되는 변수가 다를 수 있다.
4. Checkpoint age를 어떻게 관찰할 것인가
Checkpoint age 자체가 항상 별도 status variable로 깔끔하게 제공되는 것은 아니다. 운영자는 보통 SHOW ENGINE INNODB STATUS의 LOG 섹션에서 LSN 관련 값을 읽거나, status variable을 통해 간접 지표를 확인한다. 자동화된 모니터링에서는 text parsing보다 성숙한 exporter 또는 MySQL 버전에 맞는 Performance Schema/Information Schema 기반 지표를 우선 검토하는 것이 좋다.
다음 SQL은 테스트 환경에서도 실행 가능한 기본 관찰 쿼리다. Innodb_redo_log%, Innodb_log%, Innodb_buffer_pool_pages_dirty 같은 지표는 redo 및 dirty page 흐름을 해석할 때 출발점이 된다.
SHOW GLOBAL STATUS LIKE 'Innodb_redo_log%';
SHOW GLOBAL STATUS LIKE 'Innodb_log%';
SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_pages_dirty';
SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_pages_flushed';
실행 결과(MySQL 8.0.46):
mysql> SHOW GLOBAL STATUS LIKE 'Innodb_redo_log%';
+-------------------------------------+------------+
| Variable_name | Value |
+-------------------------------------+------------+
| Innodb_redo_log_read_only | OFF |
| Innodb_redo_log_uuid | 2962222029 |
| Innodb_redo_log_checkpoint_lsn | 29632342 |
| Innodb_redo_log_current_lsn | 29632342 |
| Innodb_redo_log_flushed_to_disk_lsn | 29632342 |
| 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 |
+-------------------------------------+------------+
10 rows in set (0.00 sec)
mysql> SHOW GLOBAL STATUS LIKE 'Innodb_log%';
+---------------------------+-------+
| Variable_name | Value |
+---------------------------+-------+
| Innodb_log_waits | 0 |
| Innodb_log_write_requests | 837 |
| Innodb_log_writes | 25 |
+---------------------------+-------+
3 rows in set (0.00 sec)
mysql> SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_pages_dirty';
+--------------------------------+-------+
| Variable_name | Value |
+--------------------------------+-------+
| Innodb_buffer_pool_pages_dirty | 0 |
+--------------------------------+-------+
1 row in set (0.00 sec)
mysql> SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_pages_flushed';
+----------------------------------+-------+
| Variable_name | Value |
+----------------------------------+-------+
| Innodb_buffer_pool_pages_flushed | 191 |
+----------------------------------+-------+
1 row in set (0.00 sec)
SHOW ENGINE INNODB STATUS는 사람이 읽는 진단 출력에 가깝다. 자동화 스크립트에 그대로 의존하면 버전별 포맷 변경에 취약할 수 있지만, 장애 분석 시점에는 여전히 유용하다.
SHOW ENGINE INNODB STATUS;
운영자가 특히 주의해서 볼 부분은 다음과 같다.
Log sequence number: 현재까지 생성된 redo의 위치다.Log flushed up to: redo가 어느 LSN까지 디스크에 flush되었는지 나타낸다.Pages flushed up to또는 checkpoint 관련 LSN: 데이터 파일 반영 경계와 checkpoint 진행 상태를 판단하는 데 사용된다.- dirty page 비율: buffer pool에서 아직 flush되지 않은 페이지의 압력을 보여준다.
Checkpoint age를 수식으로 단순화하면 다음과 같다.
checkpoint age ≒ current LSN - checkpoint LSN
이 값이 전체 redo capacity의 높은 비율까지 접근하면 InnoDB는 더 많은 dirty page를 flush해야 한다. 이 상태가 지속되면 쓰기 처리량은 디스크의 순차 redo write 성능이 아니라 랜덤 page flushing 성능에 의해 제한될 수 있다.
5. Redo Log 크기가 쓰기 성능에 미치는 영향
Redo Log가 충분히 크면 InnoDB는 순간적인 쓰기 피크를 buffer pool과 redo log에 흡수하고, dirty page flush를 더 부드럽게 분산할 수 있다. 이는 특히 다음과 같은 워크로드에서 중요하다.
- 짧은 시간에 대량
INSERT또는UPDATE가 몰리는 배치 작업 - secondary index가 많은 테이블의 대량 변경
- write-heavy OLTP에서 짧은 트랜잭션이 높은 동시성으로 실행되는 경우
- purge, change buffer, page split 등 내부 작업이 쓰기 부하와 겹치는 경우
Redo Log가 너무 작으면 checkpoint age가 빠르게 한계에 도달한다. 그러면 page cleaner가 평소보다 급하게 dirty page를 내려야 하고, foreground thread도 flushing에 끌려 들어갈 수 있다. 운영자는 이 현상을 단순히 “디스크가 느리다”로만 보지 말고, redo capacity와 dirty page flushing 정책의 상호작용으로 해석해야 한다.
반대로 Redo Log를 크게 늘린다고 모든 쓰기 문제가 해결되지는 않는다. 다음과 같은 병목은 redo capacity만으로 해결되지 않는다.
- commit마다 redo flush를 강하게 요구하는
innodb_flush_log_at_trx_commit=1환경에서 fsync latency가 높은 경우 - 데이터 파일 flush를 감당하지 못하는 스토리지 IOPS 부족
- 단일 hot row 또는 hot index leaf page로 인한 latch/lock 경합
- binlog fsync, replication apply, group commit 병목
- 너무 큰 트랜잭션으로 인한 undo/lock/purge 지연
따라서 Redo Log 크기 조정은 I/O capacity, commit durability 정책, binlog 설정, 트랜잭션 크기, buffer pool 크기와 함께 검토해야 한다.
6. 복구 시간과 운영 리스크
Crash recovery는 마지막 checkpoint 이후 redo log에 남아 있는 변경분을 다시 적용하여 데이터 파일을 일관된 상태로 되돌리는 과정이다. Redo Log 용량이 크고 checkpoint age가 크게 유지되던 서버가 비정상 종료되면, 재시작 시 redo apply에 더 많은 시간이 필요할 수 있다.
복구 시간은 단순히 redo byte 수에만 비례하지 않는다. 다음 요인이 함께 작용한다.
- 스토리지 읽기/쓰기 성능
- buffer pool warm-up 상태
- dirty page와 doublewrite 처리 상태
- undo rollback이 필요한 미완료 트랜잭션 규모
- MySQL minor version의 crash recovery 최적화 수준
- 파일시스템과 하드웨어 flush 특성
운영 목표가 “장애 후 1~2분 내 재기동”처럼 엄격하다면 Redo Log를 크게 늘리기 전에 실제 장애 복구 리허설을 해야 한다. 반대로 분석계 배치 서버처럼 재기동 시간보다 쓰기 처리량이 더 중요하다면 더 큰 redo capacity가 합리적일 수 있다.
Aurora MySQL에서는 저장소 계층이 Community MySQL과 다르다. Aurora는 분산 스토리지와 자체 로그 기반 저장 구조를 사용하므로 로컬 EBS에 redo/data file을 직접 관리하는 일반 MySQL과 운영 해석이 다르다. 파라미터 이름이 같거나 유사해도 실제 병목은 Aurora storage, commit path, replica lag, failover recovery, Performance Insights 지표에서 드러날 수 있다. Aurora 환경에서는 Community MySQL의 “파일 크기를 직접 늘려 checkpoint 압력을 완화한다”는 감각을 그대로 적용하기보다, 엔진 버전별 파라미터 지원 여부와 AWS 문서의 제한 사항, CloudWatch/Performance Insights 지표를 함께 확인해야 한다.
7. 실습: 작은 쓰기 부하에서 redo 관련 상태 확인
아래 예제는 작은 테이블을 만들고 데이터를 변경한 뒤, InnoDB status variable을 확인하는 재현형 예제다. 운영 서버의 checkpoint age를 그대로 재현하는 목적이 아니라, redo 및 dirty page 관련 지표를 조회하는 방법을 확인하기 위한 예제다.
DROP TABLE IF EXISTS redo_note_sample;
CREATE TABLE redo_note_sample (
id BIGINT PRIMARY KEY,
payload VARCHAR(200) NOT NULL,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB;
INSERT INTO redo_note_sample (id, payload)
VALUES
(1, REPEAT('a', 120)),
(2, REPEAT('b', 120)),
(3, REPEAT('c', 120));
UPDATE redo_note_sample
SET payload = CONCAT(payload, '-changed')
WHERE id IN (1, 2, 3);
SELECT id, CHAR_LENGTH(payload) AS payload_length
FROM redo_note_sample
ORDER BY id;
SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_pages_dirty';
SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_pages_flushed';
DROP TABLE redo_note_sample;
실행 결과(MySQL 8.0.46):
mysql> DROP TABLE IF EXISTS redo_note_sample;
Query OK, 0 rows affected (0.00 sec)
mysql> CREATE TABLE redo_note_sample (
-> id BIGINT PRIMARY KEY,
-> payload VARCHAR(200) NOT NULL,
-> updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
-> ) ENGINE=InnoDB;
Query OK, 0 rows affected (0.00 sec)
mysql> INSERT INTO redo_note_sample (id, payload)
-> VALUES
-> (1, REPEAT('a', 120)),
-> (2, REPEAT('b', 120)),
-> (3, REPEAT('c', 120));
Query OK, 3 rows affected (0.00 sec)
Records: 3 Duplicates: 0 Warnings: 0
mysql> UPDATE redo_note_sample
-> SET payload = CONCAT(payload, '-changed')
-> WHERE id IN (1, 2, 3);
Query OK, 3 rows affected (0.00 sec)
Rows matched: 3 Changed: 3 Warnings: 0
mysql> SELECT id, CHAR_LENGTH(payload) AS payload_length
-> FROM redo_note_sample
-> ORDER BY id;
+----+----------------+
| id | payload_length |
+----+----------------+
| 1 | 128 |
| 2 | 128 |
| 3 | 128 |
+----+----------------+
3 rows in set (0.00 sec)
mysql> SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_pages_dirty';
+--------------------------------+-------+
| Variable_name | Value |
+--------------------------------+-------+
| Innodb_buffer_pool_pages_dirty | 58 |
+--------------------------------+-------+
1 row in set (0.00 sec)
mysql> SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_pages_flushed';
+----------------------------------+-------+
| Variable_name | Value |
+----------------------------------+-------+
| Innodb_buffer_pool_pages_flushed | 191 |
+----------------------------------+-------+
1 row in set (0.01 sec)
mysql> DROP TABLE redo_note_sample;
Query OK, 0 rows affected (0.00 sec)
이 예제의 결과값 자체는 테스트 컨테이너의 buffer pool 상태와 background flushing 타이밍에 따라 달라질 수 있다. 중요한 것은 쓰기 작업이 발생한 뒤 dirty page와 flush 관련 상태를 함께 확인하는 습관이다. 실제 운영 분석에서는 같은 쿼리를 한 번만 실행하기보다 10초 또는 1분 간격으로 수집하여 증가율과 피크를 본다.
8. 튜닝 판단 기준
Redo Log 크기 조정은 다음 순서로 접근하는 것이 안전하다.
8.1 현재 capacity와 버전 확인
MySQL 8.0.30 이상인지, innodb_redo_log_capacity를 사용하는지 먼저 확인한다. 설정 방식이 다른 버전의 절차를 그대로 적용하면 변경이 반영되지 않았다고 오해하거나, 불필요한 재시작 절차를 수행할 수 있다.
8.2 쓰기 피크와 checkpoint 압력 확인
쓰기 피크 시간대에 다음 지표를 함께 본다.
- redo write 관련 status variable의 증가율
- dirty page 수와 dirty page 비율
- page flushed 증가율
- disk write latency와 fsync latency
- commit latency, transaction latency
SHOW ENGINE INNODB STATUS의 LSN/checkpoint 관련 값
단일 지표가 아니라 “redo 생성 속도 > page flush 지속 능력”인지 판단해야 한다.
8.3 복구 목표 시간 검토
Redo Log를 늘린 뒤 장애 복구 시간이 허용 범위 안에 있는지 검증한다. 가능하면 운영과 유사한 데이터 크기, 쓰기량, 스토리지 계층에서 테스트해야 한다. 단순 개발 컨테이너에서 빠르게 재시작된다고 운영 복구 시간도 짧다고 볼 수 없다.
8.4 변경은 단계적으로 적용
Redo capacity를 한 번에 과도하게 키우기보다, 쓰기 피크와 recovery 목표를 관찰하면서 단계적으로 조정한다. 변경 후에는 적어도 다음을 비교한다.
- 쓰기 피크 중 latency percentile 변화
- checkpoint 관련 압력 완화 여부
- dirty page 누적 패턴
- crash recovery 리허설 시간
- 스토리지 write throughput과 queue depth
9. 흔한 오해와 장애 패턴
9.1 “Redo Log가 크면 데이터가 더 안전하다”는 오해
Redo Log 크기는 내구성 그 자체가 아니라 변경분을 수용하는 로그 공간의 크기다. 내구성에는 innodb_flush_log_at_trx_commit, sync_binlog, 스토리지 flush 보장, 배터리 캐시, 파일시스템, replication topology가 함께 영향을 준다. Redo Log를 크게 한다고 commit durability가 자동으로 강해지지는 않는다.
9.2 “Checkpoint age가 크면 항상 장애다”는 오해
쓰기 피크 중 checkpoint age가 일시적으로 증가하는 것은 정상일 수 있다. 문제는 redo capacity의 높은 비율에 장시간 머물거나, 그와 동시에 쓰기 latency와 flushing 대기가 증가하는 경우다. 추세와 비율을 보아야 한다.
9.3 작은 Redo Log로 인한 쓰기 지연
Redo Log가 너무 작으면 page cleaner가 충분히 부드럽게 flush할 시간을 갖지 못한다. 이때 운영자는 iostat에서 write I/O가 높고 MySQL write latency가 튀는 현상을 볼 수 있다. Buffer pool hit ratio만 좋아도 dirty page를 내려야 하는 순간에는 쓰기 I/O가 병목이 된다.
9.4 큰 Redo Log로 인한 복구 시간 증가
대량 쓰기 직후 장애가 발생하면 마지막 checkpoint 이후 redo 적용량이 많을 수 있다. 특히 운영자가 “재시작하면 바로 살아날 것”이라고 가정한 상태에서 redo apply와 undo rollback이 길어지면 장애 대응 시간이 늘어난다.
9.5 Aurora MySQL에서의 해석 차이
Aurora는 Community MySQL과 동일한 파일 배치와 flush 경로를 운영자가 직접 제어하는 구조가 아니다. checkpoint age와 redo 관련 지표를 해석할 때는 Aurora 버전별 노출 지표, CloudWatch, Performance Insights, replica lag, storage commit latency를 함께 확인해야 한다. 또한 파라미터 그룹에서 변경 가능한 항목과 적용 방식이 Community MySQL과 다를 수 있다.
10. 운영 점검 체크리스트
- 대상 버전이 MySQL 8.0.30 이상인지 확인하고,
innodb_redo_log_capacity - MySQL 8.0.30 미만이면
innodb_log_file_size * innodb_log_files_in_group -
SHOW ENGINE INNODB STATUS -
innodb_flush_log_at_trx_commit,sync_binlog
11. 결론
Redo Log 파일 크기와 checkpoint age는 InnoDB 쓰기 성능과 장애 복구 시간을 동시에 설명하는 연결 지점이다. Redo Log를 크게 하면 쓰기 피크를 흡수할 여유가 생기지만, checkpoint age가 커진 상태에서 장애가 발생하면 복구 시간이 늘어날 수 있다. Redo Log를 작게 유지하면 복구 대상은 줄어들 수 있지만, 쓰기 피크에서 checkpoint 압력이 커져 사용자 트랜잭션이 flushing 비용을 직접 체감할 수 있다.
따라서 운영자는 Redo Log 크기를 독립 변수 하나로 보지 말고, buffer pool dirty page, page cleaner, 스토리지 latency, commit durability, crash recovery 목표를 함께 보아야 한다. 다음 단계의 InnoDB 운영 주제에서는 dirty page flushing과 page cleaner가 checkpoint age를 실제로 어떻게 낮추는지, 그리고 innodb_io_capacity 계열 설정이 쓰기 안정성에 어떤 영향을 주는지 더 세밀하게 다룰 수 있다.